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 }