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