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 */ 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 // always fire"day" property even if the user selects 204 // the already selected day again 205 jcalendar.getDayChooser().setAlwaysFireDayProperty(true); 206 207 setDateFormatString(dateFormatString); 208 setDate(date); 209 210 // Display a calendar button with an icon 211 URL iconURL = getClass().getResource( 212 "/com/toedter/calendar/images/JDateChooserIcon.gif"); 213 ImageIcon icon = new ImageIcon(iconURL); 214 215 calendarButton = new JButton(icon) { 216 private static final long serialVersionUID = -1913767779079949668L; 217 218 public boolean isFocusable() { 219 return false; 220 } 221 }; 222 calendarButton.setMargin(new Insets(0, 0, 0, 0)); 223 calendarButton.addActionListener(this); 224 225 // Alt + 'C' selects the calendar. 226 calendarButton.setMnemonic(KeyEvent.VK_C); 227 228 add(calendarButton, BorderLayout.EAST); 229 add(this.dateEditor.getUiComponent(), BorderLayout.CENTER); 230 231 calendarButton.setMargin(new Insets(0, 0, 0, 0)); 232 // calendarButton.addFocusListener(this); 233 234 popup = new JPopupMenu() { 235 private static final long serialVersionUID = -6078272560337577761L; 236 237 public void setVisible(boolean b) { 238 Boolean isCanceled = (Boolean) getClientProperty("JPopupMenu.firePopupMenuCanceled"); 239 if (b 240 || (!b && dateSelected) 241 || ((isCanceled != null) && !b && isCanceled 242 .booleanValue())) { 243 super.setVisible(b); 244 } 245 } 246 }; 247 248 popup.setLightWeightPopupEnabled(true); 249 250 popup.add(jcalendar); 251 252 lastSelectedDate = date; 253 254 // Corrects a problem that occured when the JMonthChooser's combobox is 255 // displayed, and a click outside the popup does not close it. 256 257 // The following idea was originally provided by forum user 258 // podiatanapraia: 259 changeListener = new ChangeListener() { 260 boolean hasListened = false; 261 262 public void stateChanged(ChangeEvent e) { 263 if (hasListened) { 264 hasListened = false; 265 return; 266 } 267 if (popup.isVisible() 268 && JDateChooser.this.jcalendar.monthChooser 269 .getComboBox().hasFocus()) { 270 MenuElement[] me = MenuSelectionManager.defaultManager() 271 .getSelectedPath(); 272 MenuElement[] newMe = new MenuElement[me.length + 1]; 273 newMe[0] = popup; 274 for (int i = 0; i < me.length; i++) { 275 newMe[i + 1] = me[i]; 276 } 277 hasListened = true; 278 MenuSelectionManager.defaultManager() 279 .setSelectedPath(newMe); 280 } 281 } 282 }; 283 MenuSelectionManager.defaultManager().addChangeListener(changeListener); 284 // end of code provided by forum user podiatanapraia 285 286 isInitialized = true; 287 } 288 289 /** 290 * Called when the jalendar button was pressed. 291 * 292 * @param e 293 * the action event 294 */ 295 public void actionPerformed(ActionEvent e) { 296 int x = calendarButton.getWidth() 297 - (int) popup.getPreferredSize().getWidth(); 298 int y = calendarButton.getY() + calendarButton.getHeight(); 299 300 Calendar calendar = Calendar.getInstance(); 301 Date date = dateEditor.getDate(); 302 if (date != null) { 303 calendar.setTime(date); 304 } 305 jcalendar.setCalendar(calendar); 306 popup.show(calendarButton, x, y); 307 dateSelected = false; 308 } 309 310 /** 311 * Listens for a "date" property change or a "day" property change event 312 * from the JCalendar. Updates the date editor and closes the popup. 313 * 314 * @param evt 315 * the event 316 */ 317 public void propertyChange(PropertyChangeEvent evt) { 318 319 if (evt.getPropertyName().equals("day")) { 320 if (popup.isVisible()) { 321 dateSelected = true; 322 popup.setVisible(false); 323 setDate(jcalendar.getCalendar().getTime()); 324 } 325 } else if (evt.getPropertyName().equals("date")) { 326 if (evt.getSource() == dateEditor) { 327 firePropertyChange("date", evt.getOldValue(), evt.getNewValue()); 328 } else { 329 setDate((Date) evt.getNewValue()); 330 } 331 } 332 333 // if event source was a day chooser, may need to check start/end day bounds 334 if (evt.getSource() instanceof JDayChooser) { 335 336 JDayChooser jdcSrc = (JDayChooser) evt.getSource(); 337 338 String srcName = jdcSrc.getName(); 339 String dstName = jcalendar.getDayChooser().getName(); 340 if ((srcName != null) && (dstName != null)) { 341 342 // property event from start day chooser to end day chooser 343 if (srcName.equals(JDayChooser.BEG_DAY) && (dstName.equals(JDayChooser.END_DAY))) { 344 JDayChooser jdcDst = jcalendar.getDayChooser(); 345 Calendar srcCal = Calendar.getInstance(); 346 srcCal.set(Calendar.YEAR, jdcSrc.getYear()); 347 srcCal.set(Calendar.MONTH, jdcSrc.getMonth()); 348 srcCal.set(Calendar.DAY_OF_MONTH, jdcSrc.getDay()); 349 Calendar dstCal = Calendar.getInstance(); 350 dstCal.set(Calendar.YEAR, jdcDst.getYear()); 351 dstCal.set(Calendar.MONTH, jdcDst.getMonth()); 352 dstCal.set(Calendar.DAY_OF_MONTH, jdcDst.getDay()); 353 if (srcCal.after(dstCal)) { 354 logger.debug("Adjusting: Src date exceeds Dst date..."); 355 jdcDst.setDay(srcCal.get(Calendar.DAY_OF_MONTH)); 356 jdcDst.setMonth(srcCal.get(Calendar.MONTH)); 357 jdcDst.setYear(srcCal.get(Calendar.YEAR) - 1900); 358 dateEditor.setDate(srcCal.getTime()); 359 if (getParent() != null) { 360 getParent().invalidate(); 361 } 362 } 363 } 364 365 // property event from end day chooser to start day chooser 366 if (srcName.equals(JDayChooser.END_DAY) && (dstName.equals(JDayChooser.BEG_DAY))) { 367 JDayChooser jdcDst = jcalendar.getDayChooser(); 368 Calendar srcCal = Calendar.getInstance(); 369 srcCal.set(Calendar.YEAR, jdcSrc.getYear()); 370 srcCal.set(Calendar.MONTH, jdcSrc.getMonth()); 371 srcCal.set(Calendar.DAY_OF_MONTH, jdcSrc.getDay()); 372 Calendar dstCal = Calendar.getInstance(); 373 dstCal.set(Calendar.YEAR, jdcDst.getYear()); 374 dstCal.set(Calendar.MONTH, jdcDst.getMonth()); 375 dstCal.set(Calendar.DAY_OF_MONTH, jdcDst.getDay()); 376 if (srcCal.before(dstCal)) { 377 logger.debug("Adjusting: End date preceeds Src date..."); 378 jdcDst.setDay(srcCal.get(Calendar.DAY_OF_MONTH)); 379 jdcDst.setMonth(srcCal.get(Calendar.MONTH)); 380 jdcDst.setYear(srcCal.get(Calendar.YEAR) - 1900); 381 dateEditor.setDate(srcCal.getTime()); 382 if (getParent() != null) { 383 getParent().invalidate(); 384 } 385 } 386 } 387 388 } 389 } 390 391 } 392 393 /** 394 * Updates the UI of itself and the popup. 395 */ 396 public void updateUI() { 397 super.updateUI(); 398 setEnabled(isEnabled()); 399 400 if (jcalendar != null) { 401 SwingUtilities.updateComponentTreeUI(popup); 402 } 403 } 404 405 /** 406 * Sets the locale. 407 * 408 * @param l 409 * The new locale value 410 */ 411 public void setLocale(Locale l) { 412 super.setLocale(l); 413 dateEditor.setLocale(l); 414 jcalendar.setLocale(l); 415 } 416 417 /** 418 * @return the jcalendar 419 */ 420 public JCalendar getJcalendar() { 421 return jcalendar; 422 } 423 424 /** 425 * Gets the date format string. 426 * 427 * @return Returns the dateFormatString. 428 */ 429 public String getDateFormatString() { 430 return dateEditor.getDateFormatString(); 431 } 432 433 /** 434 * Sets the date format string. E.g "MMMMM d, yyyy" will result in "July 21, 435 * 2004" if this is the selected date and locale is English. 436 * 437 * @param dfString 438 * The dateFormatString to set. 439 */ 440 public void setDateFormatString(String dfString) { 441 dateEditor.setDateFormatString(dfString); 442 invalidate(); 443 } 444 445 /** 446 * Returns the date. If the JDateChooser is started with a null date and no 447 * date was set by the user, null is returned. 448 * 449 * @return the current date 450 */ 451 public Date getDate() { 452 return dateEditor.getDate(); 453 } 454 455 /** 456 * Sets the date. Fires the property change "date" if date != null. 457 * 458 * @param date 459 * the new date. 460 */ 461 public void setDate(Date date) { 462 dateEditor.setDate(date); 463 if (getParent() != null) { 464 getParent().invalidate(); 465 } 466 } 467 468 /** 469 * Returns the calendar. If the JDateChooser is started with a null date (or 470 * null calendar) and no date was set by the user, null is returned. 471 * 472 * @return the current calendar 473 */ 474 public Calendar getCalendar() { 475 Date date = getDate(); 476 if (date == null) { 477 return null; 478 } 479 Calendar calendar = Calendar.getInstance(); 480 calendar.setTime(date); 481 return calendar; 482 } 483 484 /** 485 * Sets the calendar. Value null will set the null date on the date editor. 486 * 487 * @param calendar 488 * the calendar. 489 */ 490 public void setCalendar(Calendar calendar) { 491 if (calendar == null) { 492 dateEditor.setDate(null); 493 } else { 494 dateEditor.setDate(calendar.getTime()); 495 } 496 } 497 498 /** 499 * Enable or disable the JDateChooser. 500 * 501 * @param enabled 502 * the new enabled value 503 */ 504 public void setEnabled(boolean enabled) { 505 super.setEnabled(enabled); 506 if (dateEditor != null) { 507 dateEditor.setEnabled(enabled); 508 calendarButton.setEnabled(enabled); 509 } 510 } 511 512 /** 513 * Returns true, if enabled. 514 * 515 * @return true, if enabled. 516 */ 517 public boolean isEnabled() { 518 return super.isEnabled(); 519 } 520 521 /** 522 * Sets the icon of the buuton. 523 * 524 * @param icon 525 * The new icon 526 */ 527 public void setIcon(ImageIcon icon) { 528 calendarButton.setIcon(icon); 529 } 530 531 /** 532 * Sets the font of all subcomponents. 533 * 534 * @param font 535 * the new font 536 */ 537 public void setFont(Font font) { 538 if (isInitialized) { 539 dateEditor.getUiComponent().setFont(font); 540 jcalendar.setFont(font); 541 } 542 super.setFont(font); 543 } 544 545 /** 546 * Returns the JCalendar component. THis is usefull if you want to set some 547 * properties. 548 * 549 * @return the JCalendar 550 */ 551 public JCalendar getJCalendar() { 552 return jcalendar; 553 } 554 555 /** 556 * Returns the calendar button. 557 * 558 * @return the calendar button 559 */ 560 public JButton getCalendarButton() { 561 return calendarButton; 562 } 563 564 /** 565 * Returns the date editor. 566 * 567 * @return the date editor 568 */ 569 public IDateEditor getDateEditor() { 570 return dateEditor; 571 } 572 573 /** 574 * Sets a valid date range for selectable dates. If max is before min, the 575 * default range with no limitation is set. 576 * 577 * @param min 578 * the minimum selectable date or null (then the minimum date is 579 * set to 01\01\0001) 580 * @param max 581 * the maximum selectable date or null (then the maximum date is 582 * set to 01\01\9999) 583 */ 584 public void setSelectableDateRange(Date min, Date max) { 585 jcalendar.setSelectableDateRange(min, max); 586 dateEditor.setSelectableDateRange(jcalendar.getMinSelectableDate(), 587 jcalendar.getMaxSelectableDate()); 588 } 589 590 public void setMaxSelectableDate(Date max) { 591 jcalendar.setMaxSelectableDate(max); 592 dateEditor.setMaxSelectableDate(max); 593 } 594 595 public void setMinSelectableDate(Date min) { 596 jcalendar.setMinSelectableDate(min); 597 dateEditor.setMinSelectableDate(min); 598 } 599 600 /** 601 * Gets the maximum selectable date. 602 * 603 * @return the maximum selectable date 604 */ 605 public Date getMaxSelectableDate() { 606 return jcalendar.getMaxSelectableDate(); 607 } 608 609 /** 610 * Gets the minimum selectable date. 611 * 612 * @return the minimum selectable date 613 */ 614 public Date getMinSelectableDate() { 615 return jcalendar.getMinSelectableDate(); 616 } 617 618 /** 619 * Should only be invoked if the JDateChooser is not used anymore. Due to popup 620 * handling it had to register a change listener to the default menu 621 * selection manager which will be unregistered here. Use this method to 622 * cleanup possible memory leaks. 623 */ 624 public void cleanup() { 625 MenuSelectionManager.defaultManager().removeChangeListener(changeListener); 626 changeListener = null; 627 } 628 629 /** 630 * Creates a JFrame with a JDateChooser inside and can be used for testing. 631 * 632 * @param s 633 * The command line arguments 634 */ 635 public static void main(String[] s) { 636 JFrame frame = new JFrame("JDateChooser"); 637 JDateChooser dateChooser = new JDateChooser(); 638 // JDateChooser dateChooser = new JDateChooser(null, new Date(), null, 639 // null); 640 // dateChooser.setLocale(new Locale("de")); 641 // dateChooser.setDateFormatString("dd. MMMM yyyy"); 642 643 // dateChooser.setPreferredSize(new Dimension(130, 20)); 644 // dateChooser.setFont(new Font("Verdana", Font.PLAIN, 10)); 645 // dateChooser.setDateFormatString("yyyy-MM-dd HH:mm"); 646 647 // URL iconURL = dateChooser.getClass().getResource( 648 // "/com/toedter/calendar/images/JMonthChooserColor32.gif"); 649 // ImageIcon icon = new ImageIcon(iconURL); 650 // dateChooser.setIcon(icon); 651 652 frame.getContentPane().add(dateChooser); 653 frame.pack(); 654 frame.setVisible(true); 655 } 656 657}