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 }