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    }