001 /*
002 * $Id: BasicDatePickerUI.java 3301 2009-03-16 11:37:50Z kleopatra $
003 *
004 * Copyright 2006 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.plaf.basic;
022
023 import java.awt.BorderLayout;
024 import java.awt.Component;
025 import java.awt.Container;
026 import java.awt.Dimension;
027 import java.awt.FontMetrics;
028 import java.awt.Insets;
029 import java.awt.KeyboardFocusManager;
030 import java.awt.LayoutManager;
031 import java.awt.event.ActionEvent;
032 import java.awt.event.ActionListener;
033 import java.awt.event.FocusEvent;
034 import java.awt.event.FocusListener;
035 import java.awt.event.MouseEvent;
036 import java.awt.event.MouseListener;
037 import java.awt.event.MouseMotionListener;
038 import java.beans.PropertyChangeEvent;
039 import java.beans.PropertyChangeListener;
040 import java.beans.PropertyVetoException;
041 import java.text.DateFormat;
042 import java.text.ParseException;
043 import java.util.Calendar;
044 import java.util.Date;
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.ActionMap;
052 import javax.swing.Icon;
053 import javax.swing.InputMap;
054 import javax.swing.JButton;
055 import javax.swing.JComboBox;
056 import javax.swing.JComponent;
057 import javax.swing.JFormattedTextField;
058 import javax.swing.JPopupMenu;
059 import javax.swing.KeyStroke;
060 import javax.swing.SwingUtilities;
061 import javax.swing.UIManager;
062 import javax.swing.JFormattedTextField.AbstractFormatter;
063 import javax.swing.JFormattedTextField.AbstractFormatterFactory;
064 import javax.swing.border.Border;
065 import javax.swing.plaf.ComponentUI;
066 import javax.swing.plaf.UIResource;
067 import javax.swing.text.DefaultFormatterFactory;
068 import javax.swing.text.View;
069
070 import org.jdesktop.swingx.JXDatePicker;
071 import org.jdesktop.swingx.JXMonthView;
072 import org.jdesktop.swingx.SwingXUtilities;
073 import org.jdesktop.swingx.calendar.CalendarUtils;
074 import org.jdesktop.swingx.calendar.DatePickerFormatter;
075 import org.jdesktop.swingx.calendar.DateSelectionModel;
076 import org.jdesktop.swingx.calendar.DatePickerFormatter.DatePickerFormatterUIResource;
077 import org.jdesktop.swingx.event.DateSelectionEvent;
078 import org.jdesktop.swingx.event.DateSelectionListener;
079 import org.jdesktop.swingx.event.DateSelectionEvent.EventType;
080 import org.jdesktop.swingx.plaf.DatePickerUI;
081
082 /**
083 * The basic implementation of a <code>DatePickerUI</code>.
084 * <p>
085 *
086 *
087 * @author Joshua Outwater
088 * @author Jeanette Winzenburg
089 */
090 public class BasicDatePickerUI extends DatePickerUI {
091
092 @SuppressWarnings("all")
093 private static final Logger LOG = Logger.getLogger(BasicDatePickerUI.class
094 .getName());
095
096 protected JXDatePicker datePicker;
097 private JButton popupButton;
098 private BasicDatePickerPopup popup;
099 private Handler handler;
100 /*
101 * shared listeners
102 */
103 protected PropertyChangeListener propertyChangeListener;
104 private FocusListener focusListener;
105
106 /*
107 * listener's for the arrow button
108 */
109 protected MouseListener mouseListener;
110 protected MouseMotionListener mouseMotionListener;
111
112 /*
113 * listeners for the picker's editor
114 */
115 private ActionListener editorActionListener;
116 private EditorCancelAction editorCancelAction;
117 private PropertyChangeListener editorPropertyListener;
118
119 /**
120 * listeners for the picker's monthview
121 */
122 private DateSelectionListener monthViewSelectionListener;
123 private ActionListener monthViewActionListener;
124 private PropertyChangeListener monthViewPropertyListener;
125
126 private PopupRemover popupRemover;
127
128
129 @SuppressWarnings({"UnusedDeclaration"})
130 public static ComponentUI createUI(JComponent c) {
131 return new BasicDatePickerUI();
132 }
133
134 @Override
135 public void installUI(JComponent c) {
136 datePicker = (JXDatePicker)c;
137 datePicker.setLayout(createLayoutManager());
138 installComponents();
139 installDefaults();
140 installKeyboardActions();
141 installListeners();
142 }
143
144 @Override
145 public void uninstallUI(JComponent c) {
146 uninstallListeners();
147 uninstallKeyboardActions();
148 uninstallDefaults();
149 uninstallComponents();
150 datePicker.setLayout(null);
151 datePicker = null;
152 }
153
154 protected void installComponents() {
155
156 JFormattedTextField editor = datePicker.getEditor();
157 if (SwingXUtilities.isUIInstallable(editor)) {
158 DateFormat[] formats = getCustomFormats(editor);
159 // we are not yet listening ...
160 datePicker.setEditor(createEditor());
161 if (formats != null) {
162 datePicker.setFormats(formats);
163 }
164 }
165 updateFromEditorChanged(null, false);
166
167 popupButton = createPopupButton();
168 if (popupButton != null) {
169 // this is a trick to get hold of the client prop which
170 // prevents closing of the popup
171 JComboBox box = new JComboBox();
172 Object preventHide = box.getClientProperty("doNotCancelPopup");
173 popupButton.putClientProperty("doNotCancelPopup", preventHide);
174 datePicker.add(popupButton);
175 }
176 updateChildLocale(datePicker.getLocale());
177
178 }
179
180 /**
181 * Checks and returns custom formats on the editor, if any.
182 *
183 * @param editor the editor to check
184 * @return the custom formats uses in the editor or null if it had
185 * used defaults as defined in the datepicker properties
186 */
187 private DateFormat[] getCustomFormats(JFormattedTextField editor) {
188 DateFormat[] formats = null;
189 if (editor != null) {
190 AbstractFormatterFactory factory = editor.getFormatterFactory();
191 if (factory != null) {
192 AbstractFormatter formatter = factory.getFormatter(editor);
193 if (!(formatter instanceof DatePickerFormatterUIResource)) {
194 formats = ((DatePickerFormatter) formatter).getFormats();
195 }
196 }
197
198 }
199 return formats;
200 }
201
202 protected void uninstallComponents() {
203 JFormattedTextField editor = datePicker.getEditor();
204 if (editor != null) {
205 datePicker.remove(editor);
206 }
207
208 if (popupButton != null) {
209 datePicker.remove(popupButton);
210 popupButton = null;
211 }
212 }
213
214 /**
215 * Installs DatePicker default properties.
216 */
217 protected void installDefaults() {
218 // PENDING JW: currently this is for testing only.
219 boolean zoomable = Boolean.TRUE.equals(UIManager.get("JXDatePicker.forceZoomable"));
220 if (zoomable) {
221 datePicker.getMonthView().setZoomable(true);
222 }
223 }
224
225 protected void uninstallDefaults() {
226
227 }
228
229 protected void installKeyboardActions() {
230 // install picker's actions
231 ActionMap pickerMap = datePicker.getActionMap();
232 pickerMap.put(JXDatePicker.CANCEL_KEY, createCancelAction());
233 pickerMap.put(JXDatePicker.COMMIT_KEY, createCommitAction());
234 pickerMap.put(JXDatePicker.HOME_NAVIGATE_KEY, createHomeAction(false));
235 pickerMap.put(JXDatePicker.HOME_COMMIT_KEY, createHomeAction(true));
236 TogglePopupAction popupAction = createTogglePopupAction();
237 pickerMap.put("TOGGLE_POPUP", popupAction);
238
239 InputMap pickerInputMap = datePicker.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
240 pickerInputMap.put(KeyStroke.getKeyStroke("ENTER"), JXDatePicker.COMMIT_KEY);
241 pickerInputMap.put(KeyStroke.getKeyStroke("ESCAPE"), JXDatePicker.CANCEL_KEY);
242 // PENDING: get from LF
243 pickerInputMap.put(KeyStroke.getKeyStroke("F5"), JXDatePicker.HOME_COMMIT_KEY);
244 pickerInputMap.put(KeyStroke.getKeyStroke("shift F5"), JXDatePicker.HOME_NAVIGATE_KEY);
245 pickerInputMap.put(KeyStroke.getKeyStroke("alt DOWN"), "TOGGLE_POPUP");
246
247 installLinkPanelKeyboardActions();
248 }
249
250 protected void uninstallKeyboardActions() {
251 uninstallLinkPanelKeyboardActions(datePicker.getLinkPanel());
252 }
253
254
255 /**
256 * Installs actions and key bindings on the datePicker's linkPanel. Does
257 * nothing if the linkPanel is null.
258 *
259 * PRE: keybindings installed on picker.
260 */
261 protected void installLinkPanelKeyboardActions() {
262 if (datePicker.getLinkPanel() == null)
263 return;
264 ActionMap map = datePicker.getLinkPanel().getActionMap();
265 map.put(JXDatePicker.HOME_COMMIT_KEY, datePicker.getActionMap().get(
266 JXDatePicker.HOME_COMMIT_KEY));
267 map.put(JXDatePicker.HOME_NAVIGATE_KEY, datePicker.getActionMap().get(
268 JXDatePicker.HOME_NAVIGATE_KEY));
269 InputMap inputMap = datePicker.getLinkPanel().getInputMap(
270 JComponent.WHEN_IN_FOCUSED_WINDOW);
271 // PENDING: get from LF
272 inputMap.put(KeyStroke.getKeyStroke("F5"),
273 JXDatePicker.HOME_COMMIT_KEY);
274 inputMap.put(KeyStroke.getKeyStroke("shift F5"),
275 JXDatePicker.HOME_NAVIGATE_KEY);
276 }
277
278
279 /**
280 * Uninstalls actions and key bindings from linkPanel. Does nothing if the
281 * linkPanel is null.
282 *
283 * @param panel the component to uninstall
284 *
285 */
286 protected void uninstallLinkPanelKeyboardActions(JComponent panel) {
287 if (panel == null) return;
288 ActionMap map = panel.getActionMap();
289 map.remove(JXDatePicker.HOME_COMMIT_KEY);
290 map.remove(JXDatePicker.HOME_NAVIGATE_KEY);
291 InputMap inputMap = panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
292 // PENDING: get from LF
293 inputMap.remove(KeyStroke.getKeyStroke("F5"));
294 inputMap.remove(KeyStroke.getKeyStroke("shift F5"));
295
296 }
297
298 /**
299 * Creates and installs all listeners to all components.
300 *
301 */
302 protected void installListeners() {
303 /*
304 * create the listeners.
305 */
306 // propertyListener for datePicker
307 propertyChangeListener = createPropertyChangeListener();
308
309 // mouseListener (for popup button only) ?
310 mouseListener = createMouseListener();
311 mouseMotionListener = createMouseMotionListener();
312
313 // shared focuslistener (installed to picker and editor)
314 focusListener = createFocusListener();
315
316 // editor related listeners
317 editorActionListener = createEditorActionListener();
318 editorPropertyListener = createEditorPropertyListener();
319
320 // montheView related listeners
321 monthViewSelectionListener = createMonthViewSelectionListener();
322 monthViewActionListener = createMonthViewActionListener();
323 monthViewPropertyListener = createMonthViewPropertyListener();
324
325 popupRemover = new PopupRemover();
326 /*
327 * install the listeners
328 */
329 // picker
330 datePicker.addPropertyChangeListener(propertyChangeListener);
331 datePicker.addFocusListener(focusListener);
332
333 if (popupButton != null) {
334 // JW: which property do we want to monitor?
335 popupButton.addPropertyChangeListener(propertyChangeListener);
336 popupButton.addMouseListener(mouseListener);
337 popupButton.addMouseMotionListener(mouseMotionListener);
338 }
339
340 updateEditorListeners(null);
341 // JW the following does more than installing the listeners ..
342 // synchs properties of datepicker to monthView's
343 // prepares monthview for usage in popup
344 // synch the date
345 // Relies on being the last thing done in the install ..
346 //
347 updateFromMonthViewChanged(null);
348 }
349 /**
350 * Uninstalls and nulls all listeners which had been installed
351 * by this delegate.
352 *
353 */
354 protected void uninstallListeners() {
355 // datePicker
356 datePicker.removePropertyChangeListener(propertyChangeListener);
357 datePicker.removeFocusListener(focusListener);
358
359 // monthView
360 datePicker.getMonthView().getSelectionModel().removeDateSelectionListener(monthViewSelectionListener);
361 datePicker.getMonthView().removeActionListener(monthViewActionListener);
362 datePicker.getMonthView().removePropertyChangeListener(propertyChangeListener);
363
364 // JW: when can that be null?
365 // maybe in the very beginning? if some code calls ui.uninstall
366 // before ui.install? The editor is created by the ui.
367 if (datePicker.getEditor() != null) {
368 uninstallEditorListeners(datePicker.getEditor());
369 }
370 if (popupButton != null) {
371 popupButton.removePropertyChangeListener(propertyChangeListener);
372 popupButton.removeMouseListener(mouseListener);
373 popupButton.removeMouseMotionListener(mouseMotionListener);
374 }
375
376 popupRemover.unload();
377
378 popupRemover = null;
379 propertyChangeListener = null;
380 mouseListener = null;
381 mouseMotionListener = null;
382
383 editorActionListener = null;
384 editorPropertyListener = null;
385
386 monthViewSelectionListener = null;
387 monthViewActionListener = null;
388 monthViewPropertyListener = null;
389
390 handler = null;
391 }
392
393 // --------------------- wiring listeners
394 /**
395 * Wires the picker's monthView related listening. Removes all
396 * listeners from the given old view and adds the listeners to
397 * the current monthView. <p>
398 *
399 * @param oldMonthView
400 */
401 protected void updateMonthViewListeners(JXMonthView oldMonthView) {
402 DateSelectionModel oldModel = null;
403 if (oldMonthView != null) {
404 oldMonthView.removePropertyChangeListener(monthViewPropertyListener);
405 oldMonthView.removeActionListener(monthViewActionListener);
406 oldModel = oldMonthView.getSelectionModel();
407 }
408 datePicker.getMonthView().addPropertyChangeListener(monthViewPropertyListener);
409 datePicker.getMonthView().addActionListener(monthViewActionListener);
410 updateSelectionModelListeners(oldModel);
411 }
412
413
414 /**
415 * Wires the picker's editor related listening and actions. Removes
416 * listeners/actions from the old editor and adds them to
417 * the new editor. <p>
418 *
419 * @param oldEditor the pickers editor before the change
420 */
421 protected void updateEditorListeners(JFormattedTextField oldEditor) {
422 if (oldEditor != null) {
423 uninstallEditorListeners(oldEditor);
424 }
425 datePicker.getEditor().addPropertyChangeListener(editorPropertyListener);
426 datePicker.getEditor().addActionListener(editorActionListener);
427 datePicker.getEditor().addFocusListener(focusListener);
428 editorCancelAction = new EditorCancelAction(datePicker.getEditor());
429 }
430
431 /**
432 * Uninstalls all listeners and actions which have been installed
433 * by this delegate from the given editor.
434 *
435 * @param oldEditor the editor to uninstall.
436 */
437 private void uninstallEditorListeners(JFormattedTextField oldEditor) {
438 oldEditor.removePropertyChangeListener(editorPropertyListener);
439 oldEditor.removeActionListener(editorActionListener);
440 oldEditor.removeFocusListener(focusListener);
441 if (editorCancelAction != null) {
442 editorCancelAction.uninstall();
443 editorCancelAction = null;
444 }
445 }
446
447 /**
448 * Wires monthView's selection model listening. Removes the
449 * selection listener from the old model and add to the new model.
450 *
451 * @param oldModel the dateSelectionModel before the change, may be null.
452 */
453 protected void updateSelectionModelListeners(DateSelectionModel oldModel) {
454 if (oldModel != null) {
455 oldModel.removeDateSelectionListener(monthViewSelectionListener);
456 }
457 datePicker.getMonthView().getSelectionModel()
458 .addDateSelectionListener(monthViewSelectionListener);
459
460 }
461
462
463 // ---------------- component creation
464 /**
465 * Creates the editor used to edit the date selection. The editor is
466 * configured with the default DatePickerFormatter marked as UIResource.
467 *
468 * @return an instance of a JFormattedTextField
469 */
470 protected JFormattedTextField createEditor() {
471 JFormattedTextField f = new DefaultEditor(
472 new DatePickerFormatterUIResource(datePicker.getLocale()));
473 f.setName("dateField");
474 // this produces a fixed pref widths, looking a bit funny
475 // int columns = UIManagerExt.getInt("JXDatePicker.numColumns", null);
476 // if (columns > 0) {
477 // f.setColumns(columns);
478 // }
479 // that's always 0 as it comes from the resourcebundle
480 // f.setColumns(UIManager.getInt("JXDatePicker.numColumns"));
481 Border border = UIManager.getBorder("JXDatePicker.border");
482 if (border != null) {
483 f.setBorder(border);
484 }
485 return f;
486 }
487
488 protected JButton createPopupButton() {
489 JButton b = new JButton();
490 b.setName("popupButton");
491 b.setRolloverEnabled(false);
492 b.setMargin(new Insets(0, 3, 0, 3));
493
494 Icon icon = UIManager.getIcon("JXDatePicker.arrowIcon");
495 if (icon == null) {
496 icon = (Icon)UIManager.get("Tree.expandedIcon");
497 }
498 b.setIcon(icon);
499 b.setFocusable(false);
500 return b;
501 }
502
503 private class DefaultEditor extends JFormattedTextField implements UIResource {
504
505 private Dimension prefSizeCache;
506 private int prefEmptyInset;
507
508
509 public DefaultEditor(AbstractFormatter formatter) {
510 super(formatter);
511 }
512
513
514 @Override
515 public Dimension getPreferredSize() {
516 Dimension preferredSize = super.getPreferredSize();
517 if (getColumns() <= 0) {
518 if (getValue() == null) {
519 if (prefSizeCache != null) {
520 preferredSize.width = prefSizeCache.width;
521 preferredSize.height = prefSizeCache.height;
522 } else {
523 prefEmptyInset = preferredSize.width;
524 preferredSize.width = prefEmptyInset + getNullWidth();
525 }
526 } else {
527 preferredSize.width += Math.max(prefEmptyInset, 4);
528 prefSizeCache = new Dimension(preferredSize);
529 }
530 }
531 return preferredSize;
532 }
533
534
535 /**
536 * @return
537 */
538 private int getNullWidth() {
539 JFormattedTextField field = new JFormattedTextField(getFormatter());
540 field.setMargin(getMargin());
541 field.setBorder(getBorder());
542 field.setFont(getFont());
543 field.setValue(new Date());
544 return field.getPreferredSize().width;
545 }
546
547
548 }
549
550 // ---------------- Layout
551 /**
552 * {@inheritDoc}
553 */
554 @Override
555 public Dimension getMinimumSize(JComponent c) {
556 return getPreferredSize(c);
557 }
558
559 /**
560 * {@inheritDoc}
561 */
562 @Override
563 public Dimension getPreferredSize(JComponent c) {
564 Dimension dim = getEditorPreferredSize();
565 if (popupButton != null) {
566 dim.width += popupButton.getPreferredSize().width;
567 }
568 Insets insets = datePicker.getInsets();
569 dim.width += insets.left + insets.right;
570 dim.height += insets.top + insets.bottom;
571 return (Dimension)dim.clone();
572 }
573
574 /**
575 * Returns a preferred size for the editor. If the selected date
576 * is null, returns a reasonable minimal width. <p>
577 *
578 * PENDING: how to find the "reasonable" width is open to discussion.
579 * This implementation creates another datepicker, feeds it with
580 * the formats and asks its prefWidth. <p>
581 *
582 * That hack blows in some contexts (see Issue #763) - as a very quick
583 * replacement create a editor only.
584 *
585 * PENDING: there's a resource property JXDatePicker.numColumns - why
586 * don't we use it?
587 *
588 * @return the editor's preferred size
589 */
590 private Dimension getEditorPreferredSize() {
591 Dimension dim = datePicker.getEditor().getPreferredSize();
592 if (datePicker.getDate() == null) {
593 // JFormattedTextField field = createEditor(new DatePickerFormatterUIResource(
594 // datePicker.getFormats(),
595 // datePicker.getLocale()));
596 // field.setValue(new Date());
597 // dim.width = Math.max(field.getPreferredSize().width, dim.width);
598 // the editor tends to collapsing for empty values
599 // JW: better do this in a custom editor?
600 // seems to produce #763
601 // JXDatePicker picker = new JXDatePicker(new Date());
602 // picker.setFormats(datePicker.getFormats());
603 // dim.width = picker.getEditor().getPreferredSize().width;
604 }
605 return dim;
606 }
607
608 @Override
609 public int getBaseline(int width, int height) {
610 JFormattedTextField editor = datePicker.getEditor();
611 View rootView = editor.getUI().getRootView(editor);
612 if (rootView.getViewCount() > 0) {
613 Insets insets = editor.getInsets();
614 Insets insetsOut = datePicker.getInsets();
615 int nh = height - insets.top - insets.bottom
616 - insetsOut.top - insetsOut.bottom;
617 int y = insets.top + insetsOut.top;
618 View fieldView = rootView.getView(0);
619 int vspan = (int) fieldView.getPreferredSpan(View.Y_AXIS);
620 if (nh != vspan) {
621 int slop = nh - vspan;
622 y += slop / 2;
623 }
624 FontMetrics fm = editor.getFontMetrics(editor.getFont());
625 y += fm.getAscent();
626 return y;
627 }
628 return -1;
629 }
630
631
632 //------------------------------- controller methods/classes
633
634 /**
635 * {@inheritDoc}
636 */
637 @Override
638 public Date getSelectableDate(Date date) throws PropertyVetoException {
639 Date cleaned = date == null ? null :
640 datePicker.getMonthView().getSelectionModel().getNormalizedDate(date);
641 if (CalendarUtils.areEqual(cleaned, datePicker.getDate())) {
642 // one place to interrupt the update spiral
643 throw new PropertyVetoException("date not selectable", null);
644 }
645 if (cleaned == null) return cleaned;
646 if (datePicker.getMonthView().isUnselectableDate(cleaned)) {
647 throw new PropertyVetoException("date not selectable", null);
648 }
649 return cleaned;
650 }
651
652 //-------------------- update methods called from listeners
653 /**
654 * Updates internals after picker's date property changed.
655 */
656 protected void updateFromDateChanged() {
657 Date visibleHook = datePicker.getDate() != null ?
658 datePicker.getDate() : datePicker.getLinkDay();
659 datePicker.getMonthView().ensureDateVisible(visibleHook);
660 datePicker.getEditor().setValue(datePicker.getDate());
661 }
662
663 /**
664 * Updates date related properties in picker/monthView
665 * after a change in the editor's value. Reverts the
666 * value if the new date is unselectable.
667 *
668 * @param oldDate the editor value before the change
669 * @param newDate the editor value after the change
670 */
671 protected void updateFromValueChanged(Date oldDate, Date newDate) {
672 if ((newDate != null) && datePicker.getMonthView().isUnselectableDate(newDate)) {
673 revertValue(oldDate);
674 return;
675 }
676 // the other place to interrupt the update spiral
677 if (!CalendarUtils.areEqual(newDate, datePicker.getMonthView().getSelectionDate())) {
678 datePicker.getMonthView().setSelectionDate(newDate);
679 }
680 datePicker.setDate(newDate);
681 }
682
683 /**
684 * PENDING: currently this resets at once - but it's a no-no,
685 * because it happens during notification
686 *
687 * @param oldDate the old date to revert to
688 */
689 private void revertValue(Date oldDate) {
690 datePicker.getEditor().setValue(oldDate);
691 }
692 /**
693 * Updates date related properties picker/editor
694 * after a change in the monthView's
695 * selection.
696 *
697 * Here: does nothing if the change is intermediate.
698 *
699 * PENDNG JW: shouldn't we listen to actionEvents then?
700 *
701 * @param eventType the type of the selection change
702 * @param adjusting flag to indicate whether the the selection change
703 * is intermediate
704 */
705 protected void updateFromSelectionChanged(EventType eventType, boolean adjusting) {
706 if (adjusting) return;
707 updateEditorValue();
708 }
709
710 /**
711 * Updates internals after the picker's monthView has changed. <p>
712 *
713 * Cleans to popup. Wires the listeners. Updates date.
714 * Updates formats' timezone.
715 *
716 * @param oldMonthView the picker's monthView before the change,
717 * may be null.
718 */
719 protected void updateFromMonthViewChanged(JXMonthView oldMonthView) {
720 popup = null;
721 updateMonthViewListeners(oldMonthView);
722 TimeZone oldTimeZone = null;
723 if (oldMonthView != null) {
724 oldMonthView.setComponentInputMapEnabled(false);
725 oldTimeZone = oldMonthView.getTimeZone();
726 }
727 datePicker.getMonthView().setComponentInputMapEnabled(true);
728 updateTimeZone(oldTimeZone);
729 updateEditorValue();
730 }
731
732
733 /**
734 * Updates internals after the picker's editor property
735 * has changed. <p>
736 *
737 * Updates the picker's children. Removes the old editor and
738 * adds the new editor. Wires the editor listeners, it the flag
739 * set. Typically, this method is called during installing the
740 * componentUI with the flag set to false and true at all other
741 * moments.
742 *
743 *
744 * @param oldEditor the picker's editor before the change,
745 * may be null.
746 * @param updateListeners a flag to indicate whether the listeners
747 * are ready for usage.
748 */
749 protected void updateFromEditorChanged(JFormattedTextField oldEditor,
750 boolean updateListeners) {
751 if (oldEditor != null) {
752 datePicker.remove(oldEditor);
753 oldEditor.putClientProperty("doNotCancelPopup", null);
754 }
755 datePicker.add(datePicker.getEditor());
756 // this is a trick to get hold of the client prop which
757 // prevents closing of the popup
758 JComboBox box = new JComboBox();
759 Object preventHide = box.getClientProperty("doNotCancelPopup");
760 datePicker.getEditor().putClientProperty("doNotCancelPopup", preventHide);
761
762 updateEditorValue();
763 if (updateListeners) {
764 updateEditorListeners(oldEditor);
765 datePicker.revalidate();
766 }
767 }
768
769
770 /**
771 * Updates internals after the selection model changed.
772 *
773 * @param oldModel the model before the change.
774 */
775 protected void updateFromSelectionModelChanged(DateSelectionModel oldModel) {
776 updateSelectionModelListeners(oldModel);
777 updateEditorValue();
778 }
779
780 /**
781 * Sets the editor value to the model's selectedDate.
782 */
783 private void updateEditorValue() {
784 datePicker.getEditor().setValue(datePicker.getMonthView().getSelectionDate());
785 }
786
787 //---------------------- updating other properties
788
789
790 /**
791 * Updates properties which depend on the picker's editable. <p>
792 *
793 */
794 protected void updateFromEditableChanged() {
795 boolean isEditable = datePicker.isEditable();
796 datePicker.getMonthView().setEnabled(isEditable);
797 datePicker.getEditor().setEditable(isEditable);
798 /*
799 * PatrykRy: Commit today date is not allowed if datepicker is not editable!
800 */
801 setActionEnabled(JXDatePicker.HOME_COMMIT_KEY, isEditable);
802 // for consistency, synch navigation as well
803 setActionEnabled(JXDatePicker.HOME_NAVIGATE_KEY, isEditable);
804 }
805
806 /**
807 *
808 * @param key
809 * @param enabled
810 */
811 private void setActionEnabled(String key, boolean enabled) {
812 Action action = datePicker.getActionMap().get(key);
813 if (action != null) {
814 action.setEnabled(enabled);
815 }
816 }
817
818 /**
819 * Updates the picker's formats to the given TimeZone.
820 * @param zone the timezone to set on the formats.
821 */
822 protected void updateFormatsFromTimeZone(TimeZone zone) {
823 for (DateFormat format : datePicker.getFormats()) {
824 format.setTimeZone(zone);
825 }
826 }
827
828 /**
829 * Updates picker's timezone dependent properties on change notification
830 * from the associated monthView.
831 *
832 * PENDING JW: DatePicker needs to send notification on timezone change?
833 *
834 * @param old the timezone before the change.
835 */
836 protected void updateTimeZone(TimeZone old) {
837 updateFormatsFromTimeZone(datePicker.getTimeZone());
838 updateLinkDate();
839 }
840
841 /**
842 * Updates the picker's linkDate to be in synch with monthView's today.
843 */
844 protected void updateLinkDate() {
845 datePicker.setLinkDay(datePicker.getMonthView().getToday());
846 }
847
848 /**
849 * Called form property listener, updates all components locale, formats
850 * etc.
851 *
852 * @author PeS
853 */
854 protected void updateLocale() {
855 Locale locale = datePicker.getLocale();
856 updateFormatLocale(locale);
857 updateChildLocale(locale);
858 }
859
860 private void updateFormatLocale(Locale locale) {
861 if (locale != null) {
862 // PENDING JW: timezone?
863 if (getCustomFormats(datePicker.getEditor()) == null) {
864 datePicker.getEditor().setFormatterFactory(
865 new DefaultFormatterFactory(
866 new DatePickerFormatterUIResource(locale)));
867 }
868 }
869 }
870
871 private void updateChildLocale(Locale locale) {
872 if (locale != null) {
873 datePicker.getEditor().setLocale(locale);
874 datePicker.getLinkPanel().setLocale(locale);
875 datePicker.getMonthView().setLocale(locale);
876 }
877 }
878
879 /**
880 * @param oldLinkPanel
881 *
882 */
883 protected void updateLinkPanel(JComponent oldLinkPanel) {
884 if (oldLinkPanel != null) {
885 uninstallLinkPanelKeyboardActions(oldLinkPanel);
886 }
887 installLinkPanelKeyboardActions();
888 if (popup != null) {
889 popup.updateLinkPanel(oldLinkPanel);
890 }
891 }
892
893
894 //------------------- methods called by installed actions
895
896 /**
897 *
898 */
899 protected void commit() {
900 hidePopup();
901 try {
902 datePicker.commitEdit();
903 } catch (ParseException ex) {
904 // can't help it
905 }
906 }
907
908 /**
909 *
910 */
911 protected void cancel() {
912 hidePopup();
913 datePicker.cancelEdit();
914 }
915
916 /**
917 * PENDING: widened access for debugging - need api to
918 * control popup visibility?
919 */
920 public void hidePopup() {
921 if (popup != null) popup.setVisible(false);
922 }
923
924 public boolean isPopupVisible() {
925 if (popup != null) {
926 return popup.isVisible();
927 }
928 return false;
929 }
930 /**
931 * Navigates to linkDate. If commit, the linkDate is selected
932 * and committed. If not commit, the linkDate is scrolled to visible, if the
933 * monthview is open, does nothing for invisible monthView.
934 *
935 * @param commit boolean to indicate whether the linkDate should be
936 * selected and committed
937 */
938 protected void home(boolean commit) {
939 if (commit) {
940 Calendar cal = datePicker.getMonthView().getCalendar();
941 cal.setTime(datePicker.getLinkDay());
942 datePicker.getMonthView().setSelectionDate(cal.getTime());
943 datePicker.getMonthView().commitSelection();
944 } else {
945 datePicker.getMonthView().ensureDateVisible(datePicker.getLinkDay());
946 }
947 }
948
949 //---------------------- other stuff
950
951 /**
952 * Creates and returns the action for committing the picker's
953 * input.
954 *
955 * @return
956 */
957 private Action createCommitAction() {
958 Action action = new AbstractAction() {
959
960 public void actionPerformed(ActionEvent e) {
961 commit();
962 }
963
964 };
965 return action;
966 }
967
968 /**
969 * Creates and returns the action for cancel the picker's
970 * edit.
971 *
972 * @return
973 */
974 private Action createCancelAction() {
975 Action action = new AbstractAction() {
976
977 public void actionPerformed(ActionEvent e) {
978 cancel();
979 }
980
981 };
982 return action;
983 }
984
985 private Action createHomeAction(final boolean commit) {
986 Action action = new AbstractAction( ) {
987
988 public void actionPerformed(ActionEvent e) {
989 home(commit);
990
991 }
992
993 };
994 return action ;
995 }
996 /**
997 * The wrapper for the editor cancel action.
998 *
999 * PENDING: Need to extend TestAction?
1000 *
1001 */
1002 public class EditorCancelAction extends AbstractAction {
1003 private JFormattedTextField editor;
1004 private Action cancelAction;
1005 public static final String TEXT_CANCEL_KEY = "reset-field-edit";
1006
1007 public EditorCancelAction(JFormattedTextField field) {
1008 install(field);
1009 }
1010
1011 /**
1012 * Resets the contained editors actionMap to original and
1013 * nulls all fields. <p>
1014 * NOTE: after calling this method the action must not be
1015 * used! Create a new one for the same or another editor.
1016 *
1017 */
1018 public void uninstall() {
1019 editor.getActionMap().remove(TEXT_CANCEL_KEY);
1020 cancelAction = null;
1021 editor = null;
1022 }
1023
1024 /**
1025 * @param editor
1026 */
1027 private void install(JFormattedTextField editor) {
1028 this.editor = editor;
1029 cancelAction = editor.getActionMap().get(TEXT_CANCEL_KEY);
1030 editor.getActionMap().put(TEXT_CANCEL_KEY, this);
1031 }
1032
1033 public void actionPerformed(ActionEvent e) {
1034 cancelAction.actionPerformed(null);
1035 cancel();
1036 }
1037
1038 }
1039
1040 /**
1041 * Creates and returns the action which toggles the visibility of the popup.
1042 *
1043 * @return the action which toggles the visibility of the popup.
1044 */
1045 protected TogglePopupAction createTogglePopupAction() {
1046 return new TogglePopupAction();
1047 }
1048
1049
1050 /**
1051 * Toggles the popups visibility after preparing internal state.
1052 *
1053 *
1054 */
1055 public void toggleShowPopup() {
1056 if (popup == null) {
1057 popup = createMonthViewPopup();
1058 }
1059 if (popup.isVisible()) {
1060 popup.setVisible(false);
1061 } else {
1062 // PENDING JW: Issue 757-swing - datePicker firing focusLost on opening
1063 // not with following line - but need to run tests
1064 datePicker.getEditor().requestFocusInWindow();
1065 // datePicker.requestFocusInWindow();
1066 SwingUtilities.invokeLater(new Runnable() {
1067 public void run() {
1068 popup.show(datePicker,
1069 0, datePicker.getHeight());
1070 }
1071 });
1072 }
1073
1074 }
1075
1076 /**
1077 *
1078 */
1079 private BasicDatePickerPopup createMonthViewPopup() {
1080 BasicDatePickerPopup popup = new BasicDatePickerPopup();
1081 popup.setLightWeightPopupEnabled(datePicker.isLightWeightPopupEnabled());
1082 return popup;
1083 }
1084 /**
1085 * Action used to commit the current value in the JFormattedTextField.
1086 * This action is used by the keyboard bindings.
1087 */
1088 private class TogglePopupAction extends AbstractAction {
1089 public TogglePopupAction() {
1090 super("TogglePopup");
1091 }
1092
1093 public void actionPerformed(ActionEvent ev) {
1094 toggleShowPopup();
1095 }
1096 }
1097
1098
1099 /**
1100 * Popup component that shows a JXMonthView component along with controlling
1101 * buttons to allow traversal of the months. Upon selection of a date the
1102 * popup will automatically hide itself and enter the selection into the
1103 * editable field of the JXDatePicker.
1104 *
1105 */
1106 protected class BasicDatePickerPopup extends JPopupMenu {
1107
1108 public BasicDatePickerPopup() {
1109 setLayout(new BorderLayout());
1110 add(datePicker.getMonthView(), BorderLayout.CENTER);
1111 updateLinkPanel(null);
1112 }
1113
1114 /**
1115 * @param oldLinkPanel
1116 */
1117 public void updateLinkPanel(JComponent oldLinkPanel) {
1118 if (oldLinkPanel != null) {
1119 remove(oldLinkPanel);
1120 }
1121 if (datePicker.getLinkPanel() != null) {
1122 add(datePicker.getLinkPanel(), BorderLayout.SOUTH);
1123 }
1124
1125 }
1126 }
1127
1128 /**
1129 * PENDING: JW - I <b>really</b> hate the one-in-all. Wont touch
1130 * it for now, maybe later. As long as we have it, the new
1131 * listeners (dateSelection) are here too, for consistency.
1132 * Adding the Layout here as well is ... , IMO.
1133 */
1134 private class Handler implements LayoutManager, MouseListener, MouseMotionListener,
1135 PropertyChangeListener, DateSelectionListener, ActionListener, FocusListener {
1136
1137 //------------- implement Mouse/MotionListener
1138 private boolean _forwardReleaseEvent = false;
1139
1140 public void mouseClicked(MouseEvent ev) {
1141 }
1142
1143 public void mousePressed(MouseEvent ev) {
1144 if (!datePicker.isEnabled()) {
1145 return;
1146 }
1147 // PENDING JW: why do we need a mouseListener? the
1148 // arrowbutton should have the toggleAction installed?
1149 // Hmm... maybe doesn't ... check!
1150 // reason might be that we want to open on pressed
1151 // typically (or LF-dependent?),
1152 // the button's action is invoked on released.
1153 toggleShowPopup();
1154 }
1155
1156 public void mouseReleased(MouseEvent ev) {
1157 if (!datePicker.isEnabled() || !datePicker.isEditable()) {
1158 return;
1159 }
1160
1161 // Retarget mouse event to the month view.
1162 if (_forwardReleaseEvent) {
1163 JXMonthView monthView = datePicker.getMonthView();
1164 ev = SwingUtilities.convertMouseEvent(popupButton, ev,
1165 monthView);
1166 monthView.dispatchEvent(ev);
1167 _forwardReleaseEvent = false;
1168 }
1169 }
1170
1171 public void mouseEntered(MouseEvent ev) {
1172 }
1173
1174 public void mouseExited(MouseEvent ev) {
1175 }
1176
1177 public void mouseDragged(MouseEvent ev) {
1178 if (!datePicker.isEnabled() || !datePicker.isEditable()) {
1179 return;
1180 }
1181
1182 _forwardReleaseEvent = true;
1183
1184 if (!popup.isShowing()) {
1185 return;
1186 }
1187
1188 // Retarget mouse event to the month view.
1189 JXMonthView monthView = datePicker.getMonthView();
1190 ev = SwingUtilities.convertMouseEvent(popupButton, ev, monthView);
1191 monthView.dispatchEvent(ev);
1192 }
1193
1194 public void mouseMoved(MouseEvent ev) {
1195 }
1196 //------------------ implement DateSelectionListener
1197
1198 public void valueChanged(DateSelectionEvent ev) {
1199 updateFromSelectionChanged(ev.getEventType(), ev.isAdjusting());
1200 }
1201
1202 //------------------ implement propertyChangeListener
1203 /**
1204 * {@inheritDoc}
1205 */
1206 public void propertyChange(PropertyChangeEvent e) {
1207 if (e.getSource() == datePicker) {
1208 datePickerPropertyChange(e);
1209 } else
1210 if (e.getSource() == datePicker.getEditor()) {
1211 editorPropertyChange(e);
1212 } else
1213 if (e.getSource() == datePicker.getMonthView()) {
1214 monthViewPropertyChange(e);
1215 } else
1216 if (e.getSource() == popupButton) {
1217 buttonPropertyChange(e);
1218 } else
1219 // PENDING - move back, ...
1220 if ("value".equals(e.getPropertyName())) {
1221 throw new IllegalStateException(
1222 "editor listening is moved to dedicated propertyChangeLisener");
1223 }
1224 }
1225
1226 /**
1227 * Handles property changes from datepicker's editor.
1228 *
1229 * @param e the PropertyChangeEvent object describing the event source
1230 * and the property that has changed
1231 */
1232 private void editorPropertyChange(PropertyChangeEvent evt) {
1233 if ("value".equals(evt.getPropertyName())) {
1234 updateFromValueChanged((Date) evt.getOldValue(), (Date) evt
1235 .getNewValue());
1236 }
1237
1238 }
1239
1240 /**
1241 * Handles property changes from DatePicker.
1242 * @param e the PropertyChangeEvent object describing the
1243 * event source and the property that has changed
1244 */
1245 private void datePickerPropertyChange(PropertyChangeEvent e) {
1246 String property = e.getPropertyName();
1247 if ("date".equals(property)) {
1248 updateFromDateChanged();
1249 } else if ("enabled".equals(property)) {
1250 boolean isEnabled = datePicker.isEnabled();
1251 popupButton.setEnabled(isEnabled);
1252 datePicker.getEditor().setEnabled(isEnabled);
1253 } else if ("editable".equals(property)) {
1254 updateFromEditableChanged();
1255 } else if (JComponent.TOOL_TIP_TEXT_KEY.equals(property)) {
1256 String tip = datePicker.getToolTipText();
1257 datePicker.getEditor().setToolTipText(tip);
1258 popupButton.setToolTipText(tip);
1259 } else if (JXDatePicker.MONTH_VIEW.equals(property)) {
1260 updateFromMonthViewChanged((JXMonthView) e.getOldValue());
1261 } else if (JXDatePicker.LINK_PANEL.equals(property)) {
1262 updateLinkPanel((JComponent) e.getOldValue());
1263 } else if (JXDatePicker.EDITOR.equals(property)) {
1264 updateFromEditorChanged((JFormattedTextField) e.getOldValue(), true);
1265 } else if ("componentOrientation".equals(property)) {
1266 datePicker.revalidate();
1267 } else if ("lightWeightPopupEnabled".equals(property)) {
1268 // Force recreation of the popup when this property changes.
1269 if (popup != null) {
1270 popup.setVisible(false);
1271 }
1272 popup = null;
1273 } else if ("formats".equals(property)) {
1274 updateFormatsFromTimeZone(datePicker.getTimeZone());
1275 }
1276 else if ("locale".equals(property)) {
1277 updateLocale();
1278 }
1279 }
1280
1281 /**
1282 * Handles propertyChanges from the picker's monthView.
1283 *
1284 * @param e the PropertyChangeEvent object describing the event source
1285 * and the property that has changed
1286 */
1287 private void monthViewPropertyChange(PropertyChangeEvent e) {
1288 if ("selectionModel".equals(e.getPropertyName())) {
1289 updateFromSelectionModelChanged((DateSelectionModel) e.getOldValue());
1290 } else if ("timeZone".equals(e.getPropertyName())) {
1291 updateTimeZone((TimeZone) e.getOldValue());
1292 } else if ("today".equals(e.getPropertyName())) {
1293 updateLinkDate();
1294 }
1295 }
1296
1297 /**
1298 * Handles propertyChanges from the picker's popupButton.
1299 *
1300 * PENDING: does nothing, kept while refactoring .. which
1301 * properties from the button do we want to handle?
1302 *
1303 * @param e the PropertyChangeEvent object describing the event source
1304 * and the property that has changed.
1305 */
1306 private void buttonPropertyChange(PropertyChangeEvent e) {
1307 }
1308
1309 //-------------- implement LayoutManager
1310
1311 public void addLayoutComponent(String name, Component comp) { }
1312
1313 public void removeLayoutComponent(Component comp) { }
1314
1315 public Dimension preferredLayoutSize(Container parent) {
1316 return parent.getPreferredSize();
1317 }
1318
1319 public Dimension minimumLayoutSize(Container parent) {
1320 return parent.getMinimumSize();
1321 }
1322
1323 public void layoutContainer(Container parent) {
1324 Insets insets = datePicker.getInsets();
1325 int width = datePicker.getWidth() - insets.left - insets.right;
1326 int height = datePicker.getHeight() - insets.top - insets.bottom;
1327
1328 int popupButtonWidth = popupButton != null ? popupButton.getPreferredSize().width : 0;
1329
1330 boolean ltr = datePicker.getComponentOrientation().isLeftToRight();
1331
1332 datePicker.getEditor().setBounds(ltr ? insets.left : insets.left + popupButtonWidth,
1333 insets.top,
1334 width - popupButtonWidth,
1335 height);
1336
1337 if (popupButton != null) {
1338 popupButton.setBounds(ltr ? width - popupButtonWidth + insets.left : insets.left,
1339 insets.top,
1340 popupButtonWidth,
1341 height);
1342 }
1343 }
1344
1345 // ------------- implement actionListener (listening to monthView actionEvent)
1346
1347 public void actionPerformed(ActionEvent e) {
1348 if (e == null) return;
1349 if (e.getSource() == datePicker.getMonthView()) {
1350 monthViewActionPerformed(e);
1351 } else if (e.getSource() == datePicker.getEditor()) {
1352 editorActionPerformed(e);
1353 }
1354 }
1355
1356 /**
1357 * Listening to actionEvents fired by the picker's editor.
1358 *
1359 * @param e
1360 */
1361 private void editorActionPerformed(ActionEvent e) {
1362 // pass the commit on to the picker.
1363 commit();
1364 }
1365
1366 /**
1367 * Listening to actionEvents fired by the picker's monthView.
1368 *
1369 * @param e
1370 */
1371 private void monthViewActionPerformed(ActionEvent e) {
1372 if (JXMonthView.CANCEL_KEY.equals(e.getActionCommand())) {
1373 cancel();
1374 } else if (JXMonthView.COMMIT_KEY.equals(e.getActionCommand())) {
1375 commit();
1376 }
1377 }
1378
1379 //------------------- focusListener
1380
1381 /**
1382 * Issue #573-swingx - F2 in table doesn't focus the editor.
1383 *
1384 * Do the same as combo: manually pass-on the focus to the editor.
1385 *
1386 */
1387 public void focusGained(FocusEvent e) {
1388 if (e.isTemporary()) return;
1389 popupRemover.load();
1390 if (e.getSource() == datePicker) {
1391 datePicker.getEditor().requestFocusInWindow();
1392 }
1393 }
1394
1395 /**
1396 * #565-swingx: popup not hidden if clicked into combo.
1397 * The problem is that the combo uses the same trick as
1398 * this datepicker to prevent auto-closing of the popup
1399 * if focus is transfered back to the picker's editor.
1400 *
1401 * The idea is to hide the popup manually when the
1402 * permanentFocusOwner changes to somewhere else.
1403 *
1404 * JW: doesn't work - we only get the temporary lost,
1405 * but no permanent loss if the focus is transfered from
1406 * the focusOwner to a new permanentFocusOwner.
1407 *
1408 * OOOkaay ... looks like exclusively related to a combo:
1409 * we do get the expected focusLost if the focus is
1410 * transferred permanently from the temporary focusowner
1411 * to a new "normal" permanentFocusOwner (like a textfield),
1412 * we don't get it if transfered to a tricksing owner (like
1413 * a combo or picker). So can't do anything here.
1414 *
1415 * listen to keyboardFocusManager?
1416 */
1417 public void focusLost(FocusEvent e) {
1418
1419 }
1420 }
1421
1422 public class PopupRemover implements PropertyChangeListener {
1423
1424 private KeyboardFocusManager manager;
1425 private boolean loaded;
1426
1427 public void load() {
1428 if (manager != KeyboardFocusManager.getCurrentKeyboardFocusManager()) {
1429 unload();
1430 manager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
1431 }
1432 if (!loaded) {
1433 manager.addPropertyChangeListener("permanentFocusOwner", this);
1434 loaded = true;
1435 }
1436 }
1437
1438 /**
1439 * @param b
1440 */
1441 private void unload(boolean nullManager) {
1442 if (manager != null) {
1443 manager.removePropertyChangeListener("permanentFocusOwner", this);
1444 if (nullManager) {
1445 manager = null;
1446 }
1447 }
1448 loaded = false;
1449 }
1450
1451 public void unload() {
1452 unload(true);
1453 }
1454
1455 public void propertyChange(PropertyChangeEvent evt) {
1456 if (!isPopupVisible()) {
1457 unload(false);
1458 return;
1459 }
1460 Component comp = manager.getPermanentFocusOwner();
1461 if ((comp != null) && !SwingXUtilities.isDescendingFrom(comp, datePicker)) {
1462 unload(false);
1463 // on hiding the popup the focusmanager transfers
1464 // focus back to the old permanentFocusOwner
1465 // before showing the popup, that is the picker
1466 // or the editor. So we have to force it back ...
1467 hidePopup();
1468 comp.requestFocusInWindow();
1469 // this has no effect as focus changes are asynchronous
1470 // inHide = false;
1471 }
1472 }
1473
1474
1475 }
1476
1477
1478 // ------------------ listener creation
1479
1480 /**
1481 * Creates and returns the property change listener for the
1482 * picker's monthView
1483 * @return the listener for monthView properties
1484 */
1485 protected PropertyChangeListener createMonthViewPropertyListener() {
1486 return getHandler();
1487 }
1488
1489 /**
1490 * Creates and returns the focuslistener for picker and editor.
1491 * @return the focusListener
1492 */
1493 protected FocusListener createFocusListener() {
1494 return getHandler();
1495 }
1496
1497
1498 /**
1499 * Creates and returns the ActionListener for the picker's editor.
1500 * @return the Actionlistener for the editor.
1501 */
1502 protected ActionListener createEditorActionListener() {
1503 return getHandler();
1504 }
1505
1506 /**
1507 * Creates and returns the ActionListener for the picker's monthView.
1508 *
1509 * @return the Actionlistener for the monthView.
1510 */
1511 protected ActionListener createMonthViewActionListener() {
1512 return getHandler();
1513 }
1514
1515 /**
1516 * Returns the listener for the dateSelection.
1517 *
1518 * @return the date selection listener
1519 */
1520 protected DateSelectionListener createMonthViewSelectionListener() {
1521 return getHandler();
1522 }
1523
1524 /**
1525 * @return a propertyChangeListener listening to
1526 * editor property changes
1527 */
1528 protected PropertyChangeListener createEditorPropertyListener() {
1529 return getHandler();
1530 }
1531
1532 /**
1533 * Lazily creates and returns the shared all-mighty listener of everything
1534 *
1535 * @return the shared listener.
1536 */
1537 private Handler getHandler() {
1538 if (handler == null) {
1539 handler = new Handler();
1540 }
1541 return handler;
1542 }
1543
1544 protected PropertyChangeListener createPropertyChangeListener() {
1545 return getHandler();
1546 }
1547
1548 protected LayoutManager createLayoutManager() {
1549 return getHandler();
1550 }
1551
1552 protected MouseListener createMouseListener() {
1553 return getHandler();
1554 }
1555
1556 protected MouseMotionListener createMouseMotionListener() {
1557 return getHandler();
1558 }
1559
1560
1561 //------------ utility methods
1562
1563
1564 }