001 /*
002 * $Id: JXDatePicker.java 3311 2009-03-26 11:55:17Z kleopatra $
003 *
004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
020 */
021 package org.jdesktop.swingx;
022
023 import java.awt.Color;
024 import java.awt.ComponentOrientation;
025 import java.awt.FlowLayout;
026 import java.awt.Font;
027 import java.awt.GradientPaint;
028 import java.awt.Graphics;
029 import java.awt.event.ActionEvent;
030 import java.awt.event.ActionListener;
031 import java.awt.event.MouseAdapter;
032 import java.awt.event.MouseEvent;
033 import java.awt.event.MouseListener;
034 import java.beans.PropertyChangeEvent;
035 import java.beans.PropertyChangeListener;
036 import java.beans.PropertyVetoException;
037 import java.text.DateFormat;
038 import java.text.Format;
039 import java.text.MessageFormat;
040 import java.text.ParseException;
041 import java.text.SimpleDateFormat;
042 import java.util.Calendar;
043 import java.util.Date;
044 import java.util.EventListener;
045 import java.util.Locale;
046 import java.util.TimeZone;
047 import java.util.logging.Logger;
048
049 import javax.swing.AbstractAction;
050 import javax.swing.Action;
051 import javax.swing.JComponent;
052 import javax.swing.JFormattedTextField;
053 import javax.swing.JPanel;
054 import javax.swing.JPopupMenu;
055 import javax.swing.SwingUtilities;
056 import javax.swing.UIManager;
057 import javax.swing.JFormattedTextField.AbstractFormatter;
058 import javax.swing.JFormattedTextField.AbstractFormatterFactory;
059 import javax.swing.text.DefaultFormatterFactory;
060
061 import org.jdesktop.swingx.calendar.DatePickerFormatter;
062 import org.jdesktop.swingx.event.EventListenerMap;
063 import org.jdesktop.swingx.painter.MattePainter;
064 import org.jdesktop.swingx.plaf.DatePickerAddon;
065 import org.jdesktop.swingx.plaf.DatePickerUI;
066 import org.jdesktop.swingx.plaf.LookAndFeelAddons;
067 import org.jdesktop.swingx.plaf.UIManagerExt;
068 import org.jdesktop.swingx.util.Contract;
069
070 /**
071 * A component for entering dates with a user interaction similar to a
072 * JComboBox. The dates can be typed into a text field or selected from a
073 * JXMonthView which opens in a JXPopupMenu on user's request.
074 * <p>
075 *
076 * The date selection is controlled by the JXMonthView's DateSelectionModel.
077 * This allows the use of all its functionality in the JXDatePicker as well.
078 * F.i. restrict the selection to a date in the current or next week:
079 * <p>
080 *
081 * <pre><code>
082 * Appointment appointment = new Appointment(director,
083 * "Be sure to have polished shoes!");
084 * JXDatePicker picker = new JXDatePicker();
085 * Calendar calendar = picker.getMonthView().getCalendar();
086 * // starting today if we are in a hurry
087 * calendar.setTime(new Date());
088 * picker.getMonthView().setLowerBound(calendar.getTime());
089 * // end of next week
090 * CalendarUtils.endOfWeek(calendar);
091 * calendar.add(Calendar.WEEK_OF_YEAR);
092 * picker.getMonthView().setUpperBound(calendar.getTime());
093 * </code></pre>
094 *
095 * Similar to a JXMonthView, the JXDatePicker fires an ActionEvent when the user
096 * actively commits or cancels a selection. Interested client code can add a
097 * ActionListener to be notified by the user action.
098 *
099 * <pre><code>
100 * JXDatePicker picker = new JXDatePicker(new Date());
101 * ActionListener l = new ActionListener() {
102 * public void actionPerformed(ActionEvent e) {
103 * if (JXDatePicker.COMMIT_KEY.equals(e.getActionCommand)) {
104 * saveDate(picker.getDate());
105 * }
106 * }
107 * };
108 * picker.addActionListener(l);
109 * </code></pre>
110 *
111 * Note that ActionListener will <b>not</b> be notified if the user
112 * edits the date text without hitting the Enter key afterwards. To detect both kinds of
113 * date change, interested client code can add a PropertyChangeListener.
114 *
115 * <pre><code>
116 * JXDatePicker picker = new JXDatePicker(new Date());
117 * PropertyChangeListener listener = new PropertyChangeListener() {
118 * public void propertyChange(PropertyChangeEvent e) {
119 * if ("date".equals(e.getPropertyName()) {
120 * saveDate(picker.getDate());
121 * }
122 * }
123 * };
124 * picker.addPropertyChangeListener(listener);
125 * </code></pre>
126
127 *
128 * <p>
129 * The DateFormats used in the JXDatePicker's are initialized to the default
130 * formats of the DatePickerFormatter, as defined by the picker's resourceBundle
131 * DatePicker.properties. Application code can overwrite the picker's default
132 *
133 * <pre><code>
134 * picker.setDateFormats(myCustomFormat, myAlternativeCustomFormat);
135 * </code></pre>
136 *
137 * PENDING JW: explain what the alternatives are for (after understanding it
138 * myself ;-)
139 * <p>
140 *
141 * The selected Date is a bound property of the JXDatePicker. This allows easy
142 * binding to a property of a custom bean when using a binding framework.
143 * <p>
144 *
145 * Keybindings (as installed by the UI-Delegate)
146 * <ul>
147 * <li> ENTER commits the edited or selected value
148 * <li> ESCAPE reverts the edited or selected value
149 * <li> alt-DOWN opens the monthView popup
150 * <li> shift-F5 if monthView is visible, navigates the monthView to today
151 * (no effect otherwise)
152 * <li> F5 commits today
153 * </ul>
154 *
155 * PENDNG JW: support per-OS keybindings to be installed, currently they are
156 * hardcoded in our (single) BasicDatePickerUI.
157 *
158 * @author Joshua Outwater
159 * @author Jeanette Winzenburg
160 *
161 * @see JXMonthView
162 * @see org.jdesktop.swingx.calendar.DateSelectionModel
163 * @see DatePickerFormatter
164 *
165 */
166 public class JXDatePicker extends JComponent {
167 @SuppressWarnings("unused")
168 private static final Logger LOG = Logger.getLogger(JXDatePicker.class
169 .getName());
170 static {
171 LookAndFeelAddons.contribute(new DatePickerAddon());
172 }
173
174 /**
175 * UI Class ID
176 */
177 public static final String uiClassID = "DatePickerUI";
178
179 public static final String EDITOR = "editor";
180 public static final String MONTH_VIEW = "monthView";
181
182 public static final String LINK_PANEL = "linkPanel";
183
184 /** action command used for commit actionEvent. */
185 public static final String COMMIT_KEY = "datePickerCommit";
186 /** action command used for cancel actionEvent. */
187 public static final String CANCEL_KEY = "datePickerCancel";
188 /** action key for navigate home action */
189 public static final String HOME_NAVIGATE_KEY = "navigateHome";
190 /** action key for commit home action */
191 public static final String HOME_COMMIT_KEY = "commitHome";
192
193 private static final DateFormat[] EMPTY_DATE_FORMATS = new DateFormat[0];
194
195 /**
196 * The editable date field that displays the date
197 */
198 private JFormattedTextField _dateField;
199
200 /**
201 * Popup that displays the month view with controls for
202 * traversing/selecting dates.
203 */
204 private JPanel _linkPanel;
205 private MessageFormat _linkFormat;
206 private Date linkDate;
207
208 private JXMonthView _monthView;
209 private boolean editable = true;
210 private EventListenerMap listenerMap;
211 protected boolean lightWeightPopupEnabled = JPopupMenu.getDefaultLightWeightPopupEnabled();
212
213 private Date date;
214
215 private PropertyChangeListener monthViewListener;
216
217
218 /**
219 * Intantiates a date picker with no selection and the default
220 * <code>DatePickerFormatter</code>.
221 * <p/>
222 * The date picker is configured with the default time zone and locale
223 *
224 * @see #setTimeZone
225 * @see #getTimeZone
226 */
227 public JXDatePicker() {
228 this(null, null);
229 }
230
231
232
233 /**
234 * Intantiates a date picker using the specified time as the initial
235 * selection and the default
236 * <code>DatePickerFormatter</code>.
237 * <p/>
238 * The date picker is configured with the default time zone and locale
239 *
240 * @param selected the initially selected date
241 * @see #setTimeZone
242 * @see #getTimeZone
243 */
244 public JXDatePicker(Date selected) {
245 this(selected, null);
246 }
247
248 /**
249 * Intantiates a date picker with no selection and the default
250 * <code>DatePickerFormatter</code>.
251 * <p/>
252 * The date picker is configured with the default time zone and specified
253 * locale
254 *
255 * @param locale initial Locale
256 * @see #setTimeZone
257 * @see #getTimeZone
258 */
259 public JXDatePicker(Locale locale) {
260 this(null, locale);
261 }
262
263 /**
264 * Intantiates a date picker using the specified time as the initial
265 * selection and the default
266 * <code>DatePickerFormatter</code>.
267 * <p/>
268 * The date picker is configured with the default time zone and specified locale
269 *
270 * @param selection initially selected Date
271 * @param locale initial Locale
272 * @see #setTimeZone
273 * @see #getTimeZone
274 *
275 */
276 public JXDatePicker(Date selection, Locale locale) {
277 init();
278 if (locale != null) {
279 setLocale(locale);
280 }
281 // install the controller before setting the date
282 updateUI();
283 setDate(selection);
284 }
285
286 /**
287 * Sets the date property. <p>
288 *
289 * Does nothing if the ui vetos the new date - as might happen if
290 * the code tries to set a date which is unselectable in the
291 * monthView's context. The actual value of the new Date is controlled
292 * by the JXMonthView's DateSelectionModel. The default implementation
293 * normalizes the date to the start of the day in the model's calendar's
294 * coordinates, that is all time fields are zeroed. To keep the time fields,
295 * configure the monthView with a SingleDaySelectionModel.
296 * <p>
297 *
298 * At all "stable" (= not editing in date input field nor
299 * in the monthView) times the date is the same in the
300 * JXMonthView, this JXDatePicker and the editor. If a new Date
301 * is set, this invariant is enforced by the DatePickerUI.
302 * <p>
303 *
304 * This is a bound property.
305 *
306 *
307 * @param date the new date to set.
308 * @see #getDate()
309 * @see org.jdesktop.swingx.calendar.DateSelectionModel
310 * @see org.jdesktop.swingx.calendar.SingleDaySelectionModel
311 */
312 public void setDate(Date date) {
313 /*
314 * JW:
315 * this is a poor woman's constraint property.
316 * Introduces explicit coupling to the ui.
317 * Which is unusual at this place in code.
318 *
319 * If needed the date can be made a formal
320 * constraint property and let the ui add a
321 * VetoablePropertyListener.
322 */
323 try {
324 date = getUI().getSelectableDate(date);
325 } catch (PropertyVetoException e) {
326 return;
327 }
328 Date old = getDate();
329 this.date = date;
330 firePropertyChange("date", old, getDate());
331 }
332
333
334
335 /**
336 * Returns the currently selected date.
337 *
338 * @return Date
339 */
340 public Date getDate() {
341 return date;
342 }
343
344 /**
345 *
346 */
347 private void init() {
348 listenerMap = new EventListenerMap();
349 initMonthView();
350
351 updateLinkFormat();
352 linkDate = _monthView.getToday();
353 _linkPanel = new TodayPanel();
354 }
355
356 private void initMonthView() {
357 _monthView = new JXMonthView();
358 // _monthView.setSelectionModel(new SingleDaySelectionModel());
359 _monthView.setTraversable(true);
360 _monthView.addPropertyChangeListener(getMonthViewListener());
361 }
362
363 /**
364 * Lazily creates and returns the PropertyChangeListener which listens
365 * for model's calendar properties.
366 *
367 * @return a PropertyChangeListener for monthView's property change notification.
368 */
369 private PropertyChangeListener getMonthViewListener() {
370 if (monthViewListener == null) {
371 monthViewListener = new PropertyChangeListener() {
372
373 public void propertyChange(PropertyChangeEvent evt) {
374 if ("timeZone".equals(evt.getPropertyName())) {
375 updateTimeZone((TimeZone) evt.getOldValue(), (TimeZone) evt.getNewValue());
376 }
377
378 }
379
380 };
381 }
382 return monthViewListener;
383 }
384
385 /**
386 * Callback from monthView timezone changes. <p>
387 *
388 * NOTE: as timeZone is a bound property of this class we need to
389 * guarantee the propertyChangeNotification. As this class doesn't
390 * own this property it must listen to the owner (monthView) and
391 * re-fire the change.
392 *
393 * @param oldValue the old timezone.
394 * @param newValue the new timezone.
395 */
396 protected void updateTimeZone(TimeZone oldValue, TimeZone newValue) {
397 firePropertyChange("timeZone", oldValue, newValue);
398 }
399
400 /**
401 * Returns the look and feel (L&F) object that renders this component.
402 *
403 * @return the DatePickerUI object that renders this component
404 */
405 public DatePickerUI getUI() {
406 return (DatePickerUI) ui;
407 }
408
409 /**
410 * Sets the L&F object that renders this component.
411 *
412 * @param ui UI to use for this {@code JXDatePicker}
413 */
414 public void setUI(DatePickerUI ui) {
415 super.setUI(ui);
416 }
417
418 /**
419 * Resets the UI property with the value from the current look and feel.
420 *
421 * @see UIManager#getUI
422 */
423 @Override
424 public void updateUI() {
425 setUI((DatePickerUI) LookAndFeelAddons.getUI(this, DatePickerUI.class));
426 // JW: quick hack around #706-swingx - monthView not updated
427 // is this complete? how about editor (if not uiResource), linkPanel?
428 SwingUtilities.updateComponentTreeUI(getMonthView());
429 invalidate();
430 }
431
432 /**
433 * @inheritDoc
434 */
435 @Override
436 public String getUIClassID() {
437 return uiClassID;
438 }
439
440 /**
441 * Replaces the currently installed formatter and factory used by the
442 * editor. These string formats are defined by the
443 * <code>java.text.SimpleDateFormat</code> class. <p>
444 *
445 * Note: The given formats are internally synched to the picker's current
446 * TimeZone.
447 *
448 * @param formats zero or more not null string formats to use. Note that a
449 * null array is allowed and resets the formatter to use the
450 * localized default formats.
451 * @throws NullPointerException any array element is null.
452 * @see java.text.SimpleDateFormat
453 */
454 public void setFormats(String... formats) {
455 DateFormat[] dateFormats = null;
456 if (formats != null) {
457 Contract.asNotNull(formats,
458 "the array of format strings must not "
459 + "must not contain null elements");
460 dateFormats = new DateFormat[formats.length];
461 for (int counter = formats.length - 1; counter >= 0; counter--) {
462 dateFormats[counter] = new SimpleDateFormat(formats[counter], getLocale());
463 }
464 }
465 setFormats(dateFormats);
466 }
467
468 /**
469 * Replaces the currently installed formatter and factory used by the
470 * editor.<p>
471 *
472 * Note: The given formats are internally synched to the picker's current
473 * TimeZone.
474 *
475 * @param formats zero or more not null formats to use. Note that a
476 * null array is allowed and resets the formatter to use the
477 * localized default formats.
478 * @throws NullPointerException any of its elements is null.
479 */
480 public void setFormats(DateFormat... formats) {
481 if (formats != null) {
482 Contract.asNotNull(formats, "the array of formats " + "must not contain null elements");
483 }
484
485 DateFormat[] old = getFormats();
486 _dateField.setFormatterFactory(new DefaultFormatterFactory(
487 new DatePickerFormatter(formats, getLocale())));
488 firePropertyChange("formats", old, getFormats());
489 }
490
491 /**
492 * Returns an array of the formats used by the installed formatter
493 * if it is a subclass of <code>JXDatePickerFormatter<code>.
494 * <code>javax.swing.JFormattedTextField.AbstractFormatter</code>
495 * and <code>javax.swing.text.DefaultFormatter</code> do not have
496 * support for accessing the formats used.
497 *
498 * @return array of formats guaranteed to be not null, but might be empty.
499 */
500 public DateFormat[] getFormats() {
501 // Dig this out from the factory, if possible, otherwise return null.
502 AbstractFormatterFactory factory = _dateField.getFormatterFactory();
503 if (factory != null) {
504 AbstractFormatter formatter = factory.getFormatter(_dateField);
505 if (formatter instanceof DatePickerFormatter) {
506 return ((DatePickerFormatter) formatter).getFormats();
507 }
508 }
509 return EMPTY_DATE_FORMATS;
510 }
511
512 /**
513 * Return the <code>JXMonthView</code> used in the popup to
514 * select dates from.
515 *
516 * @return the month view component
517 */
518 public JXMonthView getMonthView() {
519 return _monthView;
520 }
521
522 /**
523 * Set the component to use the specified JXMonthView. If the new JXMonthView
524 * is configured to a different time zone it will affect the time zone of this
525 * component.
526 *
527 * @param monthView month view comopnent.
528 * @throws NullPointerException if view component is null
529 *
530 * @see #setTimeZone
531 * @see #getTimeZone
532 */
533 public void setMonthView(JXMonthView monthView) {
534 Contract.asNotNull(monthView, "monthView must not be null");
535 JXMonthView oldMonthView = getMonthView();
536 TimeZone oldTZ = getTimeZone();
537 oldMonthView.removePropertyChangeListener(getMonthViewListener());
538 _monthView = monthView;
539 getMonthView().addPropertyChangeListener(getMonthViewListener());
540 firePropertyChange(MONTH_VIEW, oldMonthView, getMonthView());
541 firePropertyChange("timeZone", oldTZ, getTimeZone());
542 }
543
544 /**
545 * Gets the time zone. This is a convenience method which returns the time zone
546 * of the JXMonthView being used.
547 *
548 * @return The <code>TimeZone</code> used by the <code>JXMonthView</code>.
549 */
550 public TimeZone getTimeZone() {
551 return _monthView.getTimeZone();
552 }
553
554 /**
555 * Sets the time zone with the given time zone value. This is a convenience
556 * method which returns the time zone of the JXMonthView being used.<p>
557 *
558 * PENDING JW: currently this property is the only property of the monthView
559 * which is exposed in this api. Not sure why it is here at all.
560 * It's asymetric (to the other properties) and as such should be either removed
561 * or the others which might be relevant to a datePicker exposed as well (probably
562 * hiding the monthView itself as an implementation detail of the ui delegate).
563 *
564 * @param tz The <code>TimeZone</code>.
565 */
566 public void setTimeZone(TimeZone tz) {
567 _monthView.setTimeZone(tz);
568 }
569
570
571 /**
572 * Returns the date shown in the LinkPanel.
573 * <p>
574 * PENDING JW: the property should be named linkDate - but that's held by the
575 * deprecated long returning method. Maybe revisit if we actually remove the other.
576 *
577 * @return the date shown in the LinkPanel.
578 */
579 public Date getLinkDay() {
580 return linkDate;
581 }
582
583 /**
584 * Set the date the link will use and the string defining a MessageFormat
585 * to format the link. If no valid date is in the editor when the popup
586 * is displayed the popup will focus on the month the linkDate is in. Calling
587 * this method will replace the currently installed linkPanel and install
588 * a new one with the requested date and format.
589 *
590 *
591 * @param linkDay the Date to set on the LinkPanel
592 * @param linkFormatString String used to format the link
593 * @see java.text.MessageFormat
594 */
595 public void setLinkDay(Date linkDay, String linkFormatString) {
596 setLinkFormat(new MessageFormat(linkFormatString));
597 setLinkDay(linkDay);
598 }
599
600 /**
601 * Sets the date shown in the TodayPanel.
602 *
603 * PENDING JW ... quick api hack for testing. Don't recreate the panel if
604 * it had been used
605 *
606 * @param linkDay the date used in the TodayPanel
607 */
608 public void setLinkDay(Date linkDay) {
609 this.linkDate = linkDay;
610 Format[] formats = getLinkFormat().getFormatsByArgumentIndex();
611 for (Format format : formats) {
612 if (format instanceof DateFormat) {
613 ((DateFormat) format).setTimeZone(getTimeZone());
614 }
615 }
616 setLinkPanel(new TodayPanel());
617 }
618
619
620 /**
621 * @param _linkFormat the _linkFormat to set
622 */
623 protected void setLinkFormat(MessageFormat _linkFormat) {
624 this._linkFormat = _linkFormat;
625 }
626
627 /**
628 * @return the _linkFormat
629 */
630 protected MessageFormat getLinkFormat() {
631 return _linkFormat;
632 }
633
634 /**
635 * Update text on the link panel.
636 *
637 */
638 private void updateLinkFormat() {
639 // PENDING JW: move to ui
640 String linkFormat = UIManagerExt.getString(
641 "JXDatePicker.linkFormat", getLocale());
642
643 if (linkFormat != null) {
644 setLinkFormat(new MessageFormat(linkFormat));
645 } else {
646 setLinkFormat(new MessageFormat("{0,date, dd MMMM yyyy}"));
647 }
648 }
649
650 /**
651 * Return the panel that is used at the bottom of the popup. The default
652 * implementation shows a link that displays the current month.
653 *
654 * @return The currently installed link panel
655 */
656 public JPanel getLinkPanel() {
657 return _linkPanel;
658 }
659
660 /**
661 * Set the panel that will be used at the bottom of the popup.
662 * PENDING JW: why insist on JPanel? JComponent would be enough?
663 *
664 * @param linkPanel The new panel to install in the popup
665 */
666 public void setLinkPanel(JPanel linkPanel) {
667 JPanel oldLinkPanel = _linkPanel;
668 _linkPanel = linkPanel;
669 firePropertyChange(LINK_PANEL, oldLinkPanel, _linkPanel);
670 }
671
672 /**
673 * Returns the formatted text field used to edit the date selection.
674 * <p>
675 * Clients should NOT use this method. It is provided to temporarily support
676 * the PLAF code.
677 *
678 * @return the formatted text field
679 */
680 // @Deprecated
681 public JFormattedTextField getEditor() {
682 return _dateField;
683 }
684
685 /**
686 * Sets the editor. <p>
687 *
688 * The default is created and set by the UI delegate.
689 * <p>
690 * Clients should NOT use this method. It is provided to temporarily support
691 * the PLAF code.
692 *
693 * @param editor the formatted input.
694 * @throws NullPointerException if editor is null.
695 *
696 * @see #getEditor
697 */
698 // @Deprecated
699 public void setEditor(JFormattedTextField editor) {
700 Contract.asNotNull(editor, "editor must not be null");
701 JFormattedTextField oldEditor = _dateField;
702 _dateField = editor;
703 firePropertyChange(EDITOR, oldEditor, _dateField);
704 }
705
706 @Override
707 public void setComponentOrientation(ComponentOrientation orientation) {
708 super.setComponentOrientation(orientation);
709 _monthView.setComponentOrientation(orientation);
710 }
711
712 /**
713 * Returns true if the current value being edited is valid.
714 *
715 * @return true if the current value being edited is valid.
716 */
717 public boolean isEditValid() {
718 return _dateField.isEditValid();
719 }
720
721 /**
722 * Commits the editor's changes and notifies ActionListeners.
723 *
724 * Forces the current value to be taken from the AbstractFormatter and
725 * set as the current value. This has no effect if there is no current
726 * AbstractFormatter installed.
727 *
728 * @throws java.text.ParseException Throws parse exception if the date
729 * can not be parsed.
730 */
731 public void commitEdit() throws ParseException {
732 try {
733 _dateField.commitEdit();
734 fireActionPerformed(COMMIT_KEY);
735 } catch (ParseException e) {
736 // re-throw
737 throw e;
738 }
739 }
740
741 /**
742 * Cancels the editor's changes and notifies ActionListeners.
743 *
744 */
745 public void cancelEdit() {
746 // hmmm... no direct api?
747 _dateField.setValue(_dateField.getValue());
748 fireActionPerformed(CANCEL_KEY);
749 }
750
751 /**
752 * Sets the editable property. If false, ...?
753 *
754 * The default value is true.
755 *
756 * @param value
757 * @see #isEditable()
758 */
759 public void setEditable(boolean value) {
760 boolean oldEditable = isEditable();
761 editable = value;
762 firePropertyChange("editable", oldEditable, editable);
763 if (editable != oldEditable) {
764 repaint();
765 }
766 }
767
768 /**
769 * Returns the editable property.
770 *
771 * @return {@code true} if the picker is editable; {@code false} otherwise
772 */
773 public boolean isEditable() {
774 return editable;
775 }
776
777 /**
778 * Returns the font that is associated with the editor of this date picker.
779 */
780 @Override
781 public Font getFont() {
782 return getEditor().getFont();
783 }
784
785 /**
786 * Set the font for the editor associated with this date picker.
787 */
788 @Override
789 public void setFont(final Font font) {
790 getEditor().setFont(font);
791 }
792
793 /**
794 * Sets the <code>lightWeightPopupEnabled</code> property, which
795 * provides a hint as to whether or not a lightweight
796 * <code>Component</code> should be used to contain the
797 * <code>JXDatePicker</code>, versus a heavyweight
798 * <code>Component</code> such as a <code>Panel</code>
799 * or a <code>Window</code>. The decision of lightweight
800 * versus heavyweight is ultimately up to the
801 * <code>JXDatePicker</code>. Lightweight windows are more
802 * efficient than heavyweight windows, but lightweight
803 * and heavyweight components do not mix well in a GUI.
804 * If your application mixes lightweight and heavyweight
805 * components, you should disable lightweight popups.
806 * The default value for the <code>lightWeightPopupEnabled</code>
807 * property is <code>true</code>, unless otherwise specified
808 * by the look and feel. Some look and feels always use
809 * heavyweight popups, no matter what the value of this property.
810 * <p/>
811 * See the article <a href="http://java.sun.com/products/jfc/tsc/articles/mixing/index.html">Mixing Heavy and Light Components</a>
812 * on <a href="http://java.sun.com/products/jfc/tsc">
813 * <em>The Swing Connection</em></a>
814 * This method fires a property changed event.
815 *
816 * @param aFlag if <code>true</code>, lightweight popups are desired
817 * @beaninfo bound: true
818 * expert: true
819 * description: Set to <code>false</code> to require heavyweight popups.
820 */
821 public void setLightWeightPopupEnabled(boolean aFlag) {
822 boolean oldFlag = lightWeightPopupEnabled;
823 lightWeightPopupEnabled = aFlag;
824 firePropertyChange("lightWeightPopupEnabled", oldFlag, lightWeightPopupEnabled);
825 }
826
827 /**
828 * Gets the value of the <code>lightWeightPopupEnabled</code>
829 * property.
830 *
831 * @return the value of the <code>lightWeightPopupEnabled</code>
832 * property
833 * @see #setLightWeightPopupEnabled
834 */
835 public boolean isLightWeightPopupEnabled() {
836 return lightWeightPopupEnabled;
837 }
838
839 /**
840 * Get the baseline for the specified component, or a value less
841 * than 0 if the baseline can not be determined. The baseline is measured
842 * from the top of the component.
843 *
844 * @param width Width of the component to determine baseline for.
845 * @param height Height of the component to determine baseline for.
846 * @return baseline for the specified component
847 */
848 public int getBaseline(int width, int height) {
849 return ((DatePickerUI) ui).getBaseline(width, height);
850 }
851
852
853 /**
854 * Adds an ActionListener.
855 * <p/>
856 * The ActionListener will receive an ActionEvent when a selection has
857 * been made.
858 *
859 * @param l The ActionListener that is to be notified
860 */
861 public void addActionListener(ActionListener l) {
862 listenerMap.add(ActionListener.class, l);
863 }
864
865 /**
866 * Removes an ActionListener.
867 *
868 * @param l The action listener to remove.
869 */
870 public void removeActionListener(ActionListener l) {
871 listenerMap.remove(ActionListener.class, l);
872 }
873
874 @Override
875 @SuppressWarnings("unchecked")
876 public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
877 java.util.List<T> listeners = listenerMap.getListeners(listenerType);
878 T[] result;
879 if (!listeners.isEmpty()) {
880 //noinspection unchecked
881 result = (T[]) java.lang.reflect.Array.newInstance(listenerType, listeners.size());
882 result = listeners.toArray(result);
883 } else {
884 result = super.getListeners(listenerType);
885 }
886 return result;
887 }
888
889
890 /**
891 * Fires an ActionEvent with the given actionCommand
892 * to all listeners.
893 */
894 protected void fireActionPerformed(String actionCommand) {
895 ActionListener[] listeners = getListeners(ActionListener.class);
896 ActionEvent e = null;
897
898 for (ActionListener listener : listeners) {
899 if (e == null) {
900 e = new ActionEvent(this,
901 ActionEvent.ACTION_PERFORMED,
902 actionCommand);
903 }
904 listener.actionPerformed(e);
905 }
906 }
907
908 /**
909 * Pes: added setLocale method to refresh link text on locale changes
910 */
911 private final class TodayPanel extends JXPanel {
912 private TodayAction todayAction;
913 private JXHyperlink todayLink;
914
915 TodayPanel() {
916 super(new FlowLayout());
917 setBackgroundPainter(new MattePainter(new GradientPaint(0, 0, new Color(238, 238, 238), 0, 1, Color.WHITE)));
918 todayAction = new TodayAction();
919 todayLink = new JXHyperlink(todayAction);
920 todayLink.addMouseListener(createDoubleClickListener());
921 Color textColor = new Color(16, 66, 104);
922 todayLink.setUnclickedColor(textColor);
923 todayLink.setClickedColor(textColor);
924 add(todayLink);
925 }
926
927 /**
928 * @return
929 */
930 private MouseListener createDoubleClickListener() {
931 MouseAdapter adapter = new MouseAdapter() {
932
933 @Override
934 public void mousePressed(MouseEvent e) {
935 if (e.getClickCount() != 2) return;
936 todayAction.select = true;
937 }
938
939 };
940 return adapter;
941 }
942
943 @Override
944 protected void paintComponent(Graphics g) {
945 super.paintComponent(g);
946 g.setColor(new Color(187, 187, 187));
947 g.drawLine(0, 0, getWidth(), 0);
948 g.setColor(new Color(221, 221, 221));
949 g.drawLine(0, 1, getWidth(), 1);
950 }
951
952 /**
953 * {@inheritDoc} <p>
954 * Overridden to update the link format and hyperlink text.
955 */
956 @Override
957 public void setLocale(Locale l) {
958 super.setLocale(l);
959 updateLinkFormat();
960 todayLink.setText(getLinkFormat().format(new Object[]{getLinkDay()}));
961 }
962
963 private final class TodayAction extends AbstractAction {
964 boolean select;
965 TodayAction() {
966 super(getLinkFormat().format(new Object[]{getLinkDay()}));
967 Calendar cal = _monthView.getCalendar();
968 cal.setTime(getLinkDay());
969 putValue(NAME, getLinkFormat().format(new Object[] {cal.getTime()}));
970 }
971
972 public void actionPerformed(ActionEvent ae) {
973 String key = select ? JXDatePicker.HOME_COMMIT_KEY : JXDatePicker.HOME_NAVIGATE_KEY;
974 select = false;
975 Action delegate = getActionMap().get(key);
976 /*
977 * PatrykRy: Commit today date only when commit action is enabled.
978 * Home navigate is always enabled.
979 */
980 if (delegate != null && delegate.isEnabled()) {
981 delegate.actionPerformed(null);
982 }
983
984 }
985 }
986 }
987
988
989 }
990