swingx-master/swingx-core/src/test/java/org/jdesktop/swingx/JXDatePickerVisualCheck.java
/*
 * Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * California 95054, U.S.A. All rights reserved.
 * 
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 * 
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */
package org.jdesktop.swingx;

import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.LayoutManager;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.util.logging.Logger;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JSpinner;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SpinnerDateModel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.JSpinner.DefaultEditor;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.text.DateFormatter;
import javax.swing.text.DefaultFormatterFactory;
import javax.swing.text.JTextComponent;

import org.jdesktop.swingx.action.AbstractActionExt;
import org.jdesktop.swingx.calendar.DateSelectionModel;
import org.jdesktop.swingx.calendar.DaySelectionModel;
import org.jdesktop.swingx.calendar.SingleDaySelectionModel;
import org.jdesktop.swingx.calendar.DateSelectionModel.SelectionMode;
import org.jdesktop.swingx.plaf.LookAndFeelAddons;
import org.jdesktop.test.VerticalLayoutPref;

/**
 * Simple tests to ensure that the {@code JXDatePicker} can be instantiated and
 * displayed.<p>
 * 
 * JW: being lazy - added visuals for <code>JXMonthView</code> as well.
 * 
 * @author Karl Schaefer
 */
public class JXDatePickerVisualCheck extends InteractiveTestCase {
    @SuppressWarnings("all")
    private static final Logger LOG = Logger
            .getLogger(JXDatePickerVisualCheck.class.getName());
    @SuppressWarnings("unused")
    private Calendar calendar;
    /** flag to decide if the menubar should be created */
    private boolean showMenu;

    public JXDatePickerVisualCheck() {
        super("JXDatePicker Test");
    }

    public static void main(String[] args) throws Exception {
       UIManager.put("JXDatePicker.forceZoomable", Boolean.TRUE);
//         setSystemLF(true);
        JXDatePickerVisualCheck test = new JXDatePickerVisualCheck();
        setLAF("Win");
        try {
//            test.runInteractiveTests();
//            test.runInteractiveTests("interactive.*PrefSize.*");
//            test.runInteractiveTests("interactive.*Keep.*");
//          test.runInteractiveTests("interactive.*Multiple.*");
//            test.runInteractiveTests("interactive.*Editable.*");
//            test.runInteractiveTests("interactive.*Enable.*");
//            test.runInteractiveTests("interactive.*Popup.*");
            test.runInteractiveTests("interactive.*NullDate.*");
//            test.runInteractiveTests("interactive.*Event.*");
        } catch (Exception e) {
            System.err.println("exception when executing interactive tests:");
            e.printStackTrace();
        }
    }

    /**
     * Quick check if DatePicker behaves correctly in vertical max
     * (yes, thougnh I don't know why: max size isn't overriden?)
     */
    public void interactivePickerMax() {
        JXDatePicker picker = new JXDatePicker();
        picker.setBorder(BorderFactory.createLineBorder(Color.RED));
        JComponent content = new JPanel();
        content.setLayout(new BoxLayout(content, BoxLayout.PAGE_AXIS));
        content.add(picker);
//        content.add(new JFormattedTextField(new Date()));
        JXFrame frame = showInFrame(content, "max size?");
        frame.setSize(frame.getWidth() * 2, frame.getHeight() * 4);
    }

    /**
     * Issue #1393-swingx: inherited popup not shown on editor/arrow button.
     * Issue #1427-swingx: right click on arrow must not open monthView
     * Issue ??: tooltip on children - okay (JW, Mar 2011).
     * Issue #1329-swingx: editor border different from textfield border in win7
     */
    public void interactiveParentPopup() {
        final JXDatePicker picker = new JXDatePicker(new Date());
        picker.setToolTipText("picker toolTip");
        picker.setInheritsPopupMenu(true);
        JComboBox compareBox = new JComboBox(new Object[]{"some", "items", "are ", "better ....."});
        compareBox.setToolTipText("combo toolTip");
        compareBox.setInheritsPopupMenu(true);
        JComboBox compareBoxEditable = new JComboBox(new Object[]{"some", "items", "are ", "better ....."});
        compareBoxEditable.setToolTipText("editable combo toolTip");
        compareBoxEditable.setEditable(true);
        compareBoxEditable.setInheritsPopupMenu(true);
        JTextField compareField = new JTextField("just for comparing");
        compareField.setInheritsPopupMenu(true);
        JPanel panel = new JPanel();
        JPopupMenu menu = new JPopupMenu();
        menu.add("dummy");
        panel.setComponentPopupMenu(menu);
        panel.add(picker);
        panel.add(compareBoxEditable);
        panel.add(compareBox);
        panel.add(compareField);
        JXFrame frame = wrapInFrame(panel, "inherited popup");
        Action printBorder = new AbstractAction("print borders ") {
            
            @Override
            public void actionPerformed(ActionEvent e) {
                String text = "picker border: " + picker.getBorder() 
                + "\n editor border: " + picker.getEditor().getBorder()
                    + "\n FormattedTextField border: " + new JFormattedTextField().getBorder()
                ;    
                LOG.info(text);
            }
        };
        addAction(frame, printBorder);
        addEnabledToggleWithChildren(frame, panel);
        addStatusMessage(frame, "right click in components to show parent popup");
        show(frame);
        LOG.info("addon " + LookAndFeelAddons.getAddon());
        
    }
    public void interactivePopupMenuListener() {
        JXDatePicker picker = new JXDatePicker();
        PopupMenuListener l = new PopupMenuListener() {
            
            @Override
            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
                LOG.info("visible");
            }
            
            @Override
            public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
                LOG.info("invisible");
            }
            
            @Override
            public void popupMenuCanceled(PopupMenuEvent e) {
                LOG.info("canceled");
            }
        };
        picker.addPopupMenuListener(l);
        showInFrame(picker, "picker: notification?");
    }
    
    public void interactivePopupMenuListenerCompare() {
        JTextArea textArea = new JTextArea(10, 20);
        JPopupMenu picker = new JPopupMenu();
        picker.add("just something");
        PopupMenuListener l = new PopupMenuListener() {
            
            @Override
            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
                LOG.info("visible");
            }
            
            @Override
            public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
                LOG.info("invisible");
            }
            
            @Override
            public void popupMenuCanceled(PopupMenuEvent e) {
                LOG.info("canceled");
            }
        };
        textArea.setComponentPopupMenu(picker);
        picker.addPopupMenuListener(l);
        showInFrame(textArea, "plain Popup: notification");
    }
    /**
     * Issue #940-swingx: support multiple selection in picker.
     * Nothing out-off-the-box, trying to manipulate the mouseEvent.
     * 
     * No success: no way to unselect
     */
    public void interactiveMultipleSelectionWithoutCtrl() {
        JXMonthView monthView = new JXMonthView(new Date()) {

            @Override
            protected void processMouseEvent(MouseEvent e) {
                // change the modifiers to believe that control key is down
//                int modifiers = e.getModifiers() | InputEvent.CTRL_DOWN_MASK;
                int modifiers = e.getModifiers() | InputEvent.CTRL_MASK;
                
                MouseEvent myME = new MouseEvent(
                    (Component) e.getSource(), 
                    e.getID(), 
                    e.getWhen(), 
                    modifiers, // my changed modifier
                    e.getX(), 
                    e.getY(), 
                    e.getXOnScreen(), 
                    e.getYOnScreen(), 
                    e.getClickCount(), 
                    e.isPopupTrigger(), 
                    e.getButton());
                super.processMouseEvent(myME);
//                super.processMouseEvent(e);
            }
            
        };
        monthView.setSelectionMode(SelectionMode.MULTIPLE_INTERVAL_SELECTION);
        JXDatePicker picker = new JXDatePicker();
        picker.setMonthView(monthView);
        JXFrame frame = showInFrame(picker, "multiple selection without ctrl");
        show(frame);
    }
    
    /**
     * Issue #940-swingx: support multiple selection in picker.
     * Nothing out-off-the-box, trying to implement custom 
     * dateSelectionModel with overridden setSelectionInterval. 
     * Looks halfway okay for mouse-interaction, completely
     * unusable with keyboard interaction.
     */
    public void interactiveMultipleSelectionTogglingModel() {
        DateSelectionModel model = new ToggleSelectionModel();
        model.setSelectionMode(SelectionMode.MULTIPLE_INTERVAL_SELECTION);
        JXMonthView monthView = new JXMonthView(new Date(), model);
        JXDatePicker picker = new JXDatePicker();
        picker.setMonthView(monthView);
        JXFrame frame = showInFrame(picker, "multiple selection - toggle");
        show(frame);
    }
    
    /**
     * Trying to interfere in setSelectionInterval. Idea from Sunacle dev forum:
     * <a href=http://forums.sun.com/thread.jspa?threadID=5431797&tstart=0> 
     * MultiSelect JList without ctrl
     * </a>
     */
    public static class ToggleSelectionModel extends DaySelectionModel {

        @Override
        public void setSelectionInterval(Date startDate, Date endDate) {
            if (!isSameDay(startDate, endDate)) {
                super.setSelectionInterval(startDate, endDate);
                return;
            }
            if (isSelected(startDate)) {
                removeSelectionInterval(startDate, startDate);
            } else {
                addSelectionInterval(startDate, startDate);
            }
        }
        
        
    }
    
    /**
     * Issue #940-swingx: support multiple selection in picker.
     * Nothing out-off-the-box, trying to implement custom 
     * dateSelectionModel. No success.
     */
    public void interactiveMultipleSelectionAddingModel() {
        DateSelectionModel model = new AddingDaySelectionModel();
        model.setSelectionMode(SelectionMode.MULTIPLE_INTERVAL_SELECTION);
        JXMonthView monthView = new JXMonthView(new Date(), model) {

            @Override
            public Date getSelectionDate() {
                if (getSelectionModel() instanceof AddingDaySelectionModel) {
                    return ((AddingDaySelectionModel) getSelectionModel()).getLastAddedDate();
                }
                return super.getSelectionDate();
            }
            
        };
        JXDatePicker picker = new JXDatePicker();
        picker.setMonthView(monthView);
        JXFrame frame = showInFrame(picker, "multiple selection");
        show(frame);
    }
    
    /**
     * Custom model which always adds selection dates.
     */
    public static class AddingDaySelectionModel extends DaySelectionModel {
        
        Date lastAdded;
        
        @Override
        public void setSelectionInterval(Date startDate, Date endDate) {
            addSelectionInterval(startDate, endDate);
        }
        
        @Override
        public void addSelectionInterval(Date startDate, Date endDate) {
            if (endDate.before(startDate)) return;
            super.addSelectionInterval(startDate, endDate);
            // PENDING: need to do better to cope with unselectables
            if (isSelected(endDate)) {
              lastAdded = endDate;
            } else if (isSelected(startDate)) {
                lastAdded = startDate;
            } else {
                lastAdded = null;
            }
        }
        
        
        @Override
        public void clearSelection() {
            lastAdded = null;
            super.clearSelection();
        }

        public Date getLastAddedDate() {
            return lastAdded;
        }
    }
    /**
     * Issue #1196-swingx: must not allow edits if disabled.
     * 
     */
    public void interactiveNotEnabled() {
        final JXDatePicker picker = new JXDatePicker();
        Calendar cal = picker.getMonthView().getCalendar();
        cal.add(Calendar.MONTH, 5);
        picker.setDate(cal.getTime());
        JXFrame frame = wrapInFrame(picker, "enabled?");
        Action enabled = new AbstractAction("toggle enabled") {
            
            @Override
            public void actionPerformed(ActionEvent e) {
                picker.setEnabled(!picker.isEnabled());
                
            }
        };
        addAction(frame, enabled);
        show(frame);
    }
    
    /**
     * Issue #910-swingx: commitToday must not be allowed if field not editable.
     * 
     */
    public void interactiveNotEditableCommitToday() {
        JXDatePicker picker = new JXDatePicker();
        Calendar cal = picker.getMonthView().getCalendar();
        cal.add(Calendar.MONTH, 5);
        picker.setDate(cal.getTime());
        picker.setEditable(false);
        showInFrame(picker, "not editable ");
    }
    
    /**
     * Issue #910-swingx: commitToday must not be allowed if field not editable.
     * 
     */
    public void interactiveNotEditableCompareTextField() {
        final JXDatePicker picker = new JXDatePicker(new Date());
        final JFormattedTextField field = new JFormattedTextField(picker.getDate());
        field.setEditable(false);
        PropertyChangeListener l = new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                if ("date".equals(evt.getPropertyName())) {
                    if (picker.getDate() != null)
                     field.setValue(picker.getDate());
                }
            }
            
        };
        picker.addPropertyChangeListener(l);
        JXPanel panel = new JXPanel();
        panel.add(picker);
        panel.add(field);
        showInFrame(panel, "not editable field ");
    }
    /**
     * From forum: have spinner with the same timezone.
     */
    public void interactiveTimeZoneFormat() {
        final JSpinner spinner = new JSpinner(new SpinnerDateModel());
        final JXDatePicker picker = new JXDatePicker();
        picker.setTimeZone(TimeZone.getTimeZone("GMT-10"));
        picker.setDate(new Date());
        spinner.setValue(picker.getDate());
        SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss", picker.getLocale());
        format.setTimeZone(picker.getTimeZone());
        JFormattedTextField textField = ((DefaultEditor) spinner.getEditor()).getTextField();
        textField.setFormatterFactory(
                new DefaultFormatterFactory(new DateFormatter(format)));
        PropertyChangeListener l = new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                if ("date".equals(evt.getPropertyName())) {
                    if (picker.getDate() != null)
                     spinner.setValue(picker.getDate());
                }
            }
            
        };
        picker.addPropertyChangeListener(l);
        JXPanel panel = new JXPanel();
        panel.add(picker);
        panel.add(spinner);
        showInFrame(panel, "formats in picker and spinner");
    }
    /**
     * Issue #565-swingx: popup not closed if open and 
     * clicking into other focus-tricksing component (like
     * picker, combo).
     * 
     * Issue #1011-swingx: popup of first picker opened on click on second
     * (mac only?)
     * 
     * And: Compare picker and combo behaviour on toggle lf.
     * 
     * Not really comparable: the combo has complete control over the popup, re-creates
     * both popup and content on install.
     * 
     */
    public void interactivePopupPickerCompareCombo() {
//      Trace14.keyboardFocusManager(true);
       final JXDatePicker picker = new JXDatePicker();
       picker.getEditor().setName("first DateField");
        JComboBox box = new JComboBox(new Object[] {"one", "twooooooo", "threeeeeeeeeeee", "GOOO!"});
        box.setEditable(true);
        JComponent comp = new JPanel();
        comp.add(picker);
        comp.add(new JXDatePicker(new Date()));
        comp.add(box);
        JXFrame frame = wrapInFrame(comp, "compare combo <-> picker, picker <-> picker", true);
        addMessage(frame, "open popup, click in other picker/combo - popup must close and other focused");
        show(frame);
    }
    
    /**
     * Issue #1011-swingx: popup of first picker opened on click on second
     * (mac only?)
     * 
     */
    public void interactivePopupTwoPickers() {
//      Trace14.keyboardFocusManager(true);
       final JXDatePicker picker = new JXDatePicker();
       picker.getEditor().setName("first DateField");
        JComponent comp = new JPanel();
        comp.add(picker);
        JXDatePicker other = new JXDatePicker(new Date());
        comp.add(other);
//        other.setFocusable(false);
//        picker.setFocusable(false);
        JXFrame frame = wrapInFrame(comp, "popup: picker <-> picker", true);
        addMessage(frame, "click on second opens popup of first (mac only?)");
        show(frame, 400, 400);
    }

    
    /**
     * Visually characterize focus behaviour.
     * 
     * Issue #577-swingx: JXDatePicker focus cleanup.
     * After commit/cancel in popup: picker's editor should be focused.
     * 
     * 
     * Issue #757-swingx: JXDatePicker inconsistent focusLost firing.
     * 
     * JXDatePicker must not fire focusLost, the picker's editor should.
     * 
     * New (?) problem: after closing focused popup by clicking into 
     * another the focus is in the picker's editor and can't be moved
     * with tab
     * - open popup, 
     * - focus popup (by clicking next month, no keyboard, nor commit/cancel)
     * - click into textfield: popup closed, picker editor has focus
     *  
     * Independent of forcing focus into picker itself or its editor on open. 
     * Looks dependent on heavyweight popup: okay on resizing the frame so 
     * that the popup fits in.
     * 
     * 
     */
    public void interactiveFocusEventOnTogglePopup() {
        JXDatePicker picker = new JXDatePicker();
        final Action togglePopup = picker.getActionMap().get("TOGGLE_POPUP");
        JComboBox box = new JComboBox(new String[] {"one", "twos"});
        box.setEditable(true);
        FocusListener l = new FocusListener() {

            @Override
            public void focusGained(FocusEvent e) {
                if (e.isTemporary()) return;
                String source = e.getSource().getClass().getSimpleName();
                LOG.info("focus gained from: " + source);
            }

            @Override
            public void focusLost(FocusEvent e) {
                if (e.isTemporary()) return;
                String source = e.getSource().getClass().getSimpleName();
                LOG.info("focus lost from: " + source);
            }};
        picker.getEditor().addFocusListener(l); 
        picker.addFocusListener(l);
        box.addFocusListener(l);
        box.getEditor().getEditorComponent().addFocusListener(l);
        JComponent panel = new JPanel();
        panel.add(box);
        panel.add(picker);
        panel.add(new JTextField("something to focus"));
        JXFrame frame = showInFrame(panel, "E: FocusEvents on editor");
        addAction(frame, togglePopup);
        frame.pack();
    }



    /**
     * Issue #568-swingx: DatePicker must not reset time fields.
     * 
     * Behaviour defined by selection model of monthView. While the default 
     * (DaySelectionModel) normalizes the dates to the start of the day in the
     * model's calendar coordinates, a SingleDaySelectionModel keeps the date as-is.
     * For now, need to explicitly set. <p>
     * 
     * Note: picking a date in the monthView still resets. And may lead to 
     * inconsistent fields (picker.date different from editor/model selected) the
     * latter being normalized? Not reproducible here - in the reported example 
     * the time-part was hard-set when getting an actionEvent from the monthView.
     */
    public void interactiveKeepTimeFields() {
        final JXDatePicker picker = new JXDatePicker();
        DateSelectionModel selectionModel = new SingleDaySelectionModel();
        picker.getMonthView().setSelectionModel(selectionModel);
        picker.setDate(new Date());
        DateFormat format = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.FULL);
        picker.setFormats(format);
        final JFormattedTextField field = new JFormattedTextField(format);
        field.setValue(picker.getDate());
        PropertyChangeListener l = new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                if ("date".equals(evt.getPropertyName())) {
                    field.setValue(evt.getNewValue());
                    LOG.info("picker/editor/monthView: " + picker.getDate() + "/" 
                           + picker.getEditor().getValue() + "/"
                           + picker.getMonthView().getSelectionDate());
                }
                
            }
            
        };
        picker.addPropertyChangeListener(l);
        Action setDate = new AbstractActionExt("set date") {

            @Override
            public void actionPerformed(ActionEvent e) {
                picker.setDate(new Date());
                
            }
            
        };
        JComponent box = Box.createHorizontalBox();
        box.add(picker);
        box.add(field);
        JXFrame frame = wrapInFrame(box, "time fields");
        addAction(frame, setDate);
        frame.pack();
        frame.setVisible(true);
        
    }

    /**
     * Issue #568-swingx: DatePicker must not reset time fields.
     * 
     * Behaviour defined by selection model of monthView. While the default 
     * (DaySelectionModel) normalizes the dates to the start of the day in the
     * model's calendar coordinates, a SingleDaySelectionModel keeps the date as-is.
     * For now, need to explicitly set. <p>
     * 
     * Note: picking a date in the monthView still resets. And may lead to 
     * inconsistent fields (picker.date different from editor/model selected) the
     * latter being normalized? Not reproducible here - in the reported example 
     * the time-part was hard-set when getting an actionEvent from the monthView.
     */
    public void interactiveKeepEndOfDay() {
        final JXDatePicker picker = new JXDatePicker();
        SingleDaySelectionModel selectionModel = new SingleDaySelectionModel() {

            @Override
            public Date getNormalizedDate(Date date) {
                return endOfDay(date);
            }
            
        };
        picker.getMonthView().setSelectionModel(selectionModel);
        picker.setDate(new Date());
        DateFormat format = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.FULL);
        picker.setFormats(format);
        final JFormattedTextField field = new JFormattedTextField(format);
        field.setValue(picker.getDate());
        PropertyChangeListener l = new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                if ("date".equals(evt.getPropertyName())) {
                    field.setValue(evt.getNewValue());
                    LOG.info("picker/editor/monthView: " + picker.getDate() + "/" 
                           + picker.getEditor().getValue() + "/"
                           + picker.getMonthView().getSelectionDate());
                }
                
            }
            
        };
        picker.addPropertyChangeListener(l);
        Action setDate = new AbstractActionExt("set date") {

            @Override
            public void actionPerformed(ActionEvent e) {
                picker.setDate(new Date());
                
            }
            
        };
        JComponent box = Box.createHorizontalBox();
        box.add(picker);
        box.add(field);
        JXFrame frame = wrapInFrame(box, "end of day");
        addAction(frame, setDate);
        frame.pack();
        frame.setVisible(true);
        
    }
    /**
     * Issue #706-swingx: picker doesn't update monthView.
     * 
     */
    public void interactiveUpdateUIPickerMonthView() {
        final JXDatePicker picker = new JXDatePicker();
        JXFrame frame = showInFrame(picker, "picker update ui");
        Action action = new AbstractActionExt("toggleUI") {
            @Override
            public void actionPerformed(ActionEvent e) {
                String uiClass = (String) UIManager.get(JXMonthView.uiClassID);
                boolean custom = uiClass.indexOf("Custom") > 0;
                if (!custom) {
                    UIManager.put(JXMonthView.uiClassID, "org.jdesktop.swingx.test.CustomMonthViewUI");
                } else {
                    UIManager.put(JXMonthView.uiClassID, null);
                }
                picker.updateUI();
                custom = !custom;
            }
            
        };
        addAction(frame, action);
        frame.pack();
    };
    

    /**
     * Issue #1292-swingx: prefsize growing on inserting text into empty editor.
     * 
     * PrefSize should be independent of empty/filled picker. 
     * If not, the initial size might appear kind of collapsed.
     *  
     * adapted code example from bug report: revalidate on text insert/remove  
     * Note: this simulation doesn't survive a LAF change - picker's editor is recreated
     * and listener not re-wired. 
     */
    public void interactivePrefSizeOnInsert() {
        final JPanel panel = new JPanel();
        final JXDatePicker picker = new JXDatePicker();
        final JFormattedTextField field = new JFormattedTextField();
        DocumentListener l = new DocumentListener() {

            @Override
            public void insertUpdate(DocumentEvent e) {
                revalidate();
                
            }
            /**
             * @param panel
             */
            private void revalidate() {
                panel.revalidate();
            }
            
            @Override
            public void removeUpdate(DocumentEvent e) {
                revalidate();
            }


            @Override
            public void changedUpdate(DocumentEvent e) {
            }
        };
        picker.getEditor().getDocument().addDocumentListener(l);
        field.getDocument().addDocumentListener(l);
        
        JComboBox box = new JComboBox(new Object[] {});
        box.setEditable(true);
        ((JTextComponent) box.getEditor().getEditorComponent()).getDocument().addDocumentListener(l);
        panel.add(field);
        panel.add(picker);
        panel.add(box);
        JXFrame frame = wrapInFrame(panel, "compare prefSize on insert");
        addMessage(frame, "type text and compare formatted/picker/combo");
        show(frame);
    }
    
    /**
     * Issue #764-swingx: JXDatePicker sizing.
     * 
     * Compare pref size with/-out date initially. 
     * - null date is slightly narrower than not null
     * - formats using the day of week are cut a bit (for "long" day names like wed)
     * - a formatted text field is slightly off, by the width of the caret
     */
    public void interactiveLocalePrefSize() {
        // wednesday - has width problems
        calendar.set(2008, Calendar.FEBRUARY, 20);
        Date date = calendar.getTime();
        String formatString = "EEE MM/dd/yyyy";
        LayoutManager layout = new VerticalLayoutPref();
        JComponent fieldsNull = new JPanel(layout);
        addFormattedTextField(fieldsNull, Locale.US, null, formatString);
        addFormattedTextField(fieldsNull, Locale.UK, null, formatString);
        addFormattedTextField(fieldsNull, Locale.GERMAN, null, formatString);
        addFormattedTextField(fieldsNull, Locale.ITALIAN, null, formatString);
        JComponent fields = new JPanel(layout);
        addFormattedTextField(fields, Locale.US, date, formatString);
        addFormattedTextField(fields, Locale.UK, date, formatString);
        addFormattedTextField(fields, Locale.GERMAN, date, formatString);
        addFormattedTextField(fields, Locale.ITALIAN, date, formatString);
        JComponent other = new JPanel(layout);
        addDatePickerWithLocaleSet(other, Locale.US, date, formatString);
        addDatePickerWithLocaleSet(other, Locale.UK, date, formatString);
        addDatePickerWithLocaleSet(other, Locale.GERMAN, date, formatString);
        addDatePickerWithLocaleSet(other, Locale.ITALIAN, date, formatString);
        JComponent comp = new JPanel(layout);
        addDatePickerWithLocaleSet(comp, Locale.US, null, formatString);
        addDatePickerWithLocaleSet(comp, Locale.UK, null, formatString);
        addDatePickerWithLocaleSet(comp, Locale.GERMAN, null, formatString);
        addDatePickerWithLocaleSet(comp, Locale.ITALIAN, null, formatString);
        JComponent outer = Box.createHorizontalBox();
        outer.add(other);
        outer.add(comp);
        outer.add(fields);
        outer.add(fieldsNull);
        JXFrame frame = wrapInFrame(outer, "Sizing DatePicker");
        addMessage(frame, "rows: locales, columns: picker/formatted field");
        show(frame);
    }
    
    /**
     * Issue #764-swingx: JXDatePicker sizing.
     * 
     * Compare pref size with/-out date initially. 
     * - null date is slightly narrower than not null
     * - formats using the day of week are cut a bit (for "long" day names like wed)
     * - a formatted text field is slightly off, by the width of the caret
     */
    public void interactiveLocalePrefSize2() {
        // wednesday - has width problems
        calendar.set(2008, Calendar.FEBRUARY, 20);
        Date date = calendar.getTime();
        String formatString = "EEE MM/dd/yyyy";
        LayoutManager layout = new VerticalLayoutPref();
        
        final JComponent german = new JPanel(layout);
        addFormattedTextField(german, Locale.GERMAN, date, formatString);
        addDatePickerWithLocaleSet(german, Locale.GERMAN, date, formatString);
        addDatePickerWithLocaleSet(german, Locale.GERMAN, null, formatString);
        addFormattedTextField(german, Locale.GERMAN, null, formatString);
        
        JComponent italian = new JPanel(layout);
        addFormattedTextField(italian, Locale.ITALIAN, date, formatString);
        addDatePickerWithLocaleSet(italian, Locale.ITALIAN, date, formatString);
        addDatePickerWithLocaleSet(italian, Locale.ITALIAN, null, formatString);
        addFormattedTextField(italian, Locale.ITALIAN, null, formatString);
        
        JComponent uk = new JPanel(layout);
        addFormattedTextField(uk, Locale.UK, date, formatString);
        addDatePickerWithLocaleSet(uk, Locale.UK, date, formatString);
        addDatePickerWithLocaleSet(uk, Locale.UK, null, formatString);
        addFormattedTextField(uk, Locale.UK, null, formatString);
        
        JComponent us = new JPanel(layout);
        addFormattedTextField(us, Locale.US, date, formatString);
        addDatePickerWithLocaleSet(us, Locale.US, date, formatString);
        addDatePickerWithLocaleSet(us, Locale.US, null, formatString);
        addFormattedTextField(us, Locale.US, null, formatString);
        
        JComponent outer = Box.createHorizontalBox();
        outer.add(us);
        outer.add(uk);
        outer.add(german);
        outer.add(italian);
        final JXFrame frame = wrapInFrame(outer, "Sizing DatePicker");
        Action pack = new AbstractAction("pack") {
            
            @Override
            public void actionPerformed(ActionEvent e) {
                JFormattedTextField field = ((JXDatePicker) german.getComponent(1)).getEditor();
                LOG.info("pref " + field.getValue() + field.getText() + field.getPreferredSize());
                frame.pack();
            }
        };
        addAction(frame, pack);
        addMessage(frame, "rows: picker/formatted field, columns: locales");
        show(frame);
    }

    
    

    /**
     * Instantiates a datePicker using the default constructor, set
     * its locale to the given and adds it to the comp.
     * @param comp the container to add the picker to
     * @param uk the locale to use.
     */
    private void addDatePickerWithLocaleSet(JComponent comp, Locale uk, Date date, String formatString) {
        JXDatePicker datePicker = new JXDatePicker(date);
        datePicker.setLocale(uk);
        if (formatString != null) {
            DateFormat format = new SimpleDateFormat(formatString, uk);
            datePicker.setFormats(format);
        }
        comp.add(datePicker);
    }

    /**
     * Instantiates a datePicker using the default constructor, set
     * its locale to the given and adds it to the comp.
     * @param comp the container to add the picker to
     * @param uk the locale to use.
     */
    private void addFormattedTextField(JComponent comp, Locale uk, Date date, String formatString) {
        JFormattedTextField datePicker;
        if (formatString != null) {
            DateFormat format = new SimpleDateFormat(formatString, uk);
            datePicker = new JFormattedTextField(format);
        } else {
            datePicker = new JFormattedTextField();
        }
        datePicker.setValue(date);
        comp.add(datePicker);
    }


    /**
     * Issue #665-swingx: make JXDatePicker Locale-aware.
     * 
     * Here: instantiate the picker with a non-default locale. 
     * Check that the dates in LinkPanel and editor 
     * are formatted as appropriate for the Locale 
     */
    public void interactiveLocaleConstructor() {
        JComponent other = new JPanel();
        // wednesday - has width problems
        calendar.set(2008, Calendar.FEBRUARY, 20);
        Date date = calendar.getTime();
        addDatePickerWithLocaleConstructor(other, Locale.US, date);
        addDatePickerWithLocaleConstructor(other, Locale.UK, date);
        addDatePickerWithLocaleConstructor(other, Locale.GERMAN, date);
        addDatePickerWithLocaleConstructor(other, Locale.ITALIAN, date);
        JComponent comp = new JPanel();
        addDatePickerWithLocaleConstructor(comp, Locale.US, null);
        addDatePickerWithLocaleConstructor(comp, Locale.UK, null);
        addDatePickerWithLocaleConstructor(comp, Locale.GERMAN, null);
        addDatePickerWithLocaleConstructor(comp, Locale.ITALIAN, null);
        JComponent outer = Box.createVerticalBox();
        outer.add(other);
        outer.add(comp);
        showInFrame(outer, "Localized DatePicker: constructor");
    }

    /**
     * Instantiates a datePicker using the constructor with the given locale and
     * adds it to the comp.
     * @param comp the container to add the picker to
     * @param uk the locale to use.
     */
    private void addDatePickerWithLocaleConstructor(JComponent comp, Locale uk, Date date) {
        JXDatePicker datePicker = new JXDatePicker(uk);
        datePicker.setDate(date);
        comp.add(new JLabel(uk.getDisplayName()));
        comp.add(datePicker);
    }

    /**
     * Issue #665-swingx: make JXDatePicker Locale-aware.
     * 
     * Tests reaction to default locales set via both JComponent.setDefault and
     * Locale.setDefault. Going that way, catches the locales fine.
     * 
     * Also Issue #681-swingx - the first row of days in the monthview
     * overlaps with the day names for locales which have the monday as the 
     * first day of week. 
     */
    public void interactiveLocaleDefault() {
        JComponent comp = new JPanel();
        Locale old = addDatePickerWithLocale(comp, Locale.US);
        addDatePickerWithLocale(comp, Locale.UK);
        addDatePickerWithLocale(comp, Locale.GERMAN);
        addDatePickerWithLocale(comp, Locale.ITALIAN);
        showInFrame(comp, "DatePicker takes default Locale");
        setLocale(old);
    }

    /**
     * Sets the default Locale to the given, instantiates a JXDatePicker with
     * default Locale and adds it to the given component. Returns the previous 
     * default Locale.
     *  
     * @param comp the container to add the picker to
     * @param uk the new default Locale
     *  
     * @return the previous default Locale
     */
    private Locale addDatePickerWithLocale(JComponent comp, Locale uk) {
        Locale old = setLocale(uk);
        JXDatePicker datePicker = new JXDatePicker();
        comp.add(new JLabel(uk.getDisplayName()));
        comp.add(datePicker);
        return old;
    }

    /**
     * Sets default Locale (on Locale and JComponent) to the given Locale and
     * returns the previous default.
     * 
     * @param locale the default Locale to set.
     * @return the previous default.
     */
    private Locale setLocale(Locale locale) {
        Locale old = JComponent.getDefaultLocale();
        JComponent.setDefaultLocale(locale);
        Locale.setDefault(locale);
        return old;
    }


    /**
     * Issue #566-swingx: JXRootPane eats picker's popup esc.
     * to reproduce: open the picker's popup the press esc -
     * not closed. Same with combo is working.
     *
     */
    public void interactiveXRootPaneEatsEscape() {
        JXDatePicker picker = new JXDatePicker();
        JComboBox box = new JComboBox(new String[] {"one", "twos"});
        box.setEditable(true);
        JComponent panel = new JPanel();
        panel.add(picker);
        panel.add(box);
        showInFrame(panel, "Escape key");
    }
    

    /**
     * visual check that toggling the panel adds/removes it
     * and installs the keybindings.
     *
     */
    public void interactiveLinkPanelSet() {
        final JXDatePicker picker = new JXDatePicker();
        final JPanel panel = picker.getLinkPanel();
        // initial null okay
        JXFrame frame = showInFrame(picker, "null panel");
        Action toggleLinkPanel = new AbstractAction("toggleLinkPanel <-> null") {

            @Override
            public void actionPerformed(ActionEvent e) {
                boolean hasLinkPanel = picker.getLinkPanel() != null;
                picker.setLinkPanel(hasLinkPanel ? null : panel);
            }
            
        };
        addAction(frame, toggleLinkPanel);
        frame.pack();
    }
  
    /**
     * Checking PropertyChangeEvent: report in forum that no event for date fired.
     * here not reproducible.. problem of WizardPage?
     */
    public void interactivePCEvent() {
        JXDatePicker picker = new JXDatePicker(new Date());
        JFormattedTextField textField = new JFormattedTextField(DateFormat.getDateInstance());
        textField.setValue(new Date());
        
        PropertyChangeListener l = new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent e) {
                LOG.info("got pce from: " + e.getSource().getClass().getSimpleName() + 
                        "\n" + e.getPropertyName() + e.getNewValue());
                
            }
            
        };
        textField.addPropertyChangeListener(l);
        picker.addPropertyChangeListener(l);
//        picker.getMonthView().addActionListener(l);
        JPanel panel = new JPanel();
        panel.add(textField);
        panel.add(picker);
        JTabbedPane tab = new JTabbedPane();
        tab.add("fields", panel);
        tab.add("dummy", new JTextField("dummy"));
        JXFrame frame = showInFrame(tab, "Compare propertChange events: keyboard/mouse");
        frame.pack();
        
    }
    

    /**
     * Issue #235-swingx: action events
     * 
     * Compare textfield, formatted, picker, combo after keyboard.
     * 
     * TextField
     * - simple field fires on enter always
     * - formatted fire on enter if value had been edited
     *
     * ComboBox
     * - fires on enter always
     * - fires on click in dropdown
     * 
     * Calendar widgets after cleanup: 
     * 
     * Picker
     * - fires "datePickerCommit" on click (actually released) into monthView
     * - fires "datePickerCommit"/-"Cancel" on enter/escape, both in input field
     * and if popup is open
     * 
     * MonthView
     * - fires "monthViewCommit" on click (actually released)
     * - fires "monthViewCommit"/-"Cancel" on enter/esc 
     * 
     * 
     */
    public void interactiveActionEventSetAction() {
        JXDatePicker picker = new JXDatePicker();
        JTextField simpleField = new JTextField("simple field");
        JFormattedTextField textField = new JFormattedTextField(DateFormat.getDateInstance());
        textField.setValue(new Date());
        JComboBox box = new JComboBox(new Object[] {"one", "two", "three"});
        box.setEditable(true);
        JComboBox nonEditableBox = new JComboBox(new Object[] {"one", "two", "three"});
        final Action l = new AbstractActionExt("recived") {

            @Override
            public void actionPerformed(ActionEvent e) {
                LOG.info("got action from: " + e.getSource().getClass().getName() + 
                        "\n" + e);
            }
            
        };
        simpleField.setAction(l);
        textField.setAction(l);
        // picker doesn't have action-related api
        picker.addActionListener(l);
//        picker.getMonthView().addActionListener(l);
        box.setAction(l);
        nonEditableBox.setAction(l);
        
        JPanel panel = new JPanel();
        panel.add(simpleField);
        panel.add(textField);
        panel.add(picker);
        panel.add(box);
        panel.add(new JCheckBox(l));
        panel.add(nonEditableBox);
        JXFrame frame = wrapInFrame(panel, "SetAction - Compare action events: keyboard/mouse");
        Action toggleEnabled = new AbstractActionExt("toggleEnabledAction") {

            @Override
            public void actionPerformed(ActionEvent e) {
                l.setEnabled(!l.isEnabled());
            }
            
        };
        addAction(frame, toggleEnabled);
        show(frame);
    }

    /**
     * Issue #235-swingx: action events
     * 
     * Compare textfield, formatted, picker and combo: programatic change.
     * - only combo fires
     * 
     */
    public void interactiveActionEventSetValue() {
        final JXDatePicker picker = new JXDatePicker();
        final JTextField simpleField = new JTextField("simple field");
        final JFormattedTextField textField = new JFormattedTextField(DateFormat.getDateInstance());
        textField.setValue(new Date());
        final JComboBox box = new JComboBox(new Object[] {"one", "two", "three"});
        box.setEditable(true);
        
        ActionListener l = new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                LOG.info("got action from: " + e.getSource().getClass().getName() + 
                        "\n" + e);
            }
            
        };
        simpleField.addActionListener(l);
        textField.addActionListener(l);
        picker.addActionListener(l);
        picker.getMonthView().addActionListener(l);
        box.addActionListener(l);
        Action action = new AbstractAction("set new value") {
            int dayToAdd = 1;
            @Override
            public void actionPerformed(ActionEvent e) {
                Calendar cal = Calendar.getInstance();
                cal.add(Calendar.DAY_OF_MONTH, dayToAdd++);
                Date date = cal.getTime();
                String text = DateFormat.getDateInstance().format(date);
                simpleField.setText(text);
                textField.setValue(date);
                picker.setDate(date);
                box.setSelectedItem(text);
            }
            
        };
        
        JPanel panel = new JPanel();
        panel.add(simpleField);
        panel.add(textField);
        panel.add(picker);
        panel.add(box);
        
        JXFrame frame = showInFrame(panel, "Compare action events: programmatic change");
        addAction(frame, action);
        frame.pack();
    }


    /**
     * Issue #99-swingx: null date and opening popup forces selection.
     * Status? Looks fixed..
     * 
     * Sizing issue if init with null date
     */
    public void interactiveNullDate() {
        JXDatePicker picker = new JXDatePicker();
        showInFrame(picker, "null date in picker");
    }
    
    
    /**
     * Issue #??-swingx: not possible to clear the date.
     * reported on SO: http://stackoverflow.com/q/15133981/203657
     * 
     * not reproducible (maybe was a coding error, missed api)
     * 
     */
    public void interactiveSetNullDate() {
        final JXDatePicker picker = new JXDatePicker(new Date());
        JXFrame frame = wrapInFrame(picker, "null date in picker");
        Action clearDate = new AbstractAction("clear") {

            @Override
            public void actionPerformed(ActionEvent e) {
                // set the date to null
                picker.setDate(null);
                // more model related (if a handle to the model is available
                // clear the selection on the monthView
//                DateSelectionModel model = picker.getMonthView().getSelectionModel();
//                model.clearSelection();
                // don't: the null will not be committed!
//                picker.getEditor().setText("");
//                System.out.println(picker.getDate());
            }
            
        };
        addAction(frame, clearDate);
        show(frame);
    }

    
    /**
     * something weird's going on: the picker's date must be null
     * after setting a monthView with null selection. It is, until
     * shown?
     * Looks fixed during synch control cleanup in datePicker.
     */
    public void interactiveShowPickerSetMonthNull() {
        JXDatePicker picker = new JXDatePicker();
        JXMonthView intervalForPicker = new JXMonthView();
        intervalForPicker.setSelectionMode(SelectionMode.SINGLE_INTERVAL_SELECTION);
        picker.setMonthView(intervalForPicker);
        assertNull(picker.getDate());
        showInFrame(picker, "empty selection in monthView");
        assertNull(picker.getDate());
    }
    
    public void interactiveDatePickerDisplay() {
        JXDatePicker datePicker = new JXDatePicker();
        showInFrame(datePicker, "show date picker");
    }
    

    
    @Override
    protected void setUp() throws Exception {
        calendar = Calendar.getInstance();
    }

    private static class SetPlafAction extends AbstractAction {
        private String plaf;
        
        public SetPlafAction(String name, String plaf) {
            super(name);
            this.plaf = plaf;
        }
        
        /**
         * {@inheritDoc}
         */
        @Override
        public void actionPerformed(ActionEvent e) {
            try {
                Component c = (Component) e.getSource();
                Window w = null;
                
                for (Container p = c.getParent(); p != null; p = p instanceof JPopupMenu ? (Container) ((JPopupMenu) p)
                        .getInvoker() : p.getParent()) {
                    if (p instanceof Window) {
                        w = (Window) p;
                    }
                }
                
                UIManager.setLookAndFeel(plaf);
                SwingUtilities.updateComponentTreeUI(w);
                w.pack();
            } catch (ClassNotFoundException e1) {
                e1.printStackTrace();
            } catch (InstantiationException e1) {
                e1.printStackTrace();
            } catch (IllegalAccessException e1) {
                e1.printStackTrace();
            } catch (UnsupportedLookAndFeelException e1) {
                e1.printStackTrace();
            }
        }
    }
    
    private JMenuBar createMenuBar() {
        LookAndFeelInfo[] plafs = UIManager.getInstalledLookAndFeels();
        JMenuBar bar = new JMenuBar();
        JMenu menu = new JMenu("Set L&F");
        
        for (LookAndFeelInfo info : plafs) {
            menu.add(new SetPlafAction(info.getName(), info.getClassName()));
        }
        
        bar.add(menu);
        
        return bar;
    }
    
    @Override
    public JXFrame wrapInFrame(JComponent component, String title) {
        JXFrame frame = super.wrapInFrame(component, title);
        if (showMenu) {
            frame.setJMenuBar(createMenuBar());
        }
        return frame;
    }
    


    /**
     * Do nothing, make the test runner happy
     * (would output a warning without a test fixture).
     *
     */
    public void testDummy() {
        
    }
    
}
