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