/* * $Id$ * * 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.Dimension; import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.text.DateFormatSymbols; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.NumberFormat; 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.Box; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JDesktopPane; import javax.swing.JFormattedTextField; import javax.swing.JInternalFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JSpinner; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.SpinnerDateModel; import javax.swing.plaf.UIResource; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableModel; import org.jdesktop.swingx.calendar.CalendarUtils; import org.jdesktop.swingx.calendar.DatePickerFormatter; import org.jdesktop.swingx.table.DatePickerCellEditor; import org.junit.Test; /** * Known issues of <code>JXDatePicker</code> and picker related * formats. * * @author Jeanette Winzenburg */ public class JXDatePickerIssues extends InteractiveTestCase { @SuppressWarnings("all") private static final Logger LOG = Logger.getLogger(JXDatePickerIssues.class .getName()); public static void main(String[] args) { // setSystemLF(true); // Trace14.keyboardFocusManager(false); JXDatePickerIssues test = new JXDatePickerIssues(); try { // test.runInteractiveTests(); // test.runInteractiveTests("interactive.*UpdateUI.*"); // test.runInteractiveTests("interactive.*Symbols.*"); test.runInteractiveTests("interactive.*AsEditor.*"); } catch (Exception e) { System.err.println("exception when executing interactive tests:"); e.printStackTrace(); } } /** * Issue #1372-swingx: DatePickerCellEditor on 2 different tables with clickCountToStart = 1 * throws IllegalComponentStateException * * Metal only? * Same behaviour for JTable (and terminateEditOnFocusLost) and JXTable * hot fix: backing out if parent null - leaves arrowButton in pressed? */ public void interactiveIllegalComponentStateAsEditor() { TableModel model = new DefaultTableModel(10, 2) { /** * @inherited <p> */ @Override public Class<?> getColumnClass(int columnIndex) { return Date.class; } }; JTable one = createTableWithEditor(model, "picker 1 "); JTable other = createTableWithEditor(model, "picker 2"); showWithScrollingInFrame(one, other, "click to left of cell"); } private JTable createTableWithEditor(TableModel model, String name) { JTable table = new JXTable(model); // table.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); DatePickerCellEditor editor = new DatePickerCellEditor(); editor.getTableCellEditorComponent(table, null, false, 0, 0).setName(name); editor.setClickCountToStart(1); table.setDefaultEditor(Date.class, editor); return table; } public void interactiveDateSymbols() { for (Locale locale : Locale.getAvailableLocales()) { LOG.info("locale " + locale + locale.getDisplayCountry()); } DecimalFormat df = (DecimalFormat) NumberFormat.getInstance(new Locale("ar", "KW")); DecimalFormatSymbols dfs = new DecimalFormatSymbols(new Locale("ar", "KW"));//df.getDecimalFormatSymbols(); LOG.info("" + df + " zero " + dfs.getZeroDigit()); LOG.info("" + dfs); df.setDecimalFormatSymbols(dfs); @SuppressWarnings("unused") DateFormatSymbols dateSymbols = null; // set the beginning of the range to Arabic digits // dfs.setZeroDigit('\u0660'); // df.setDecimalFormatSymbols(dfs); JXDatePicker picker = new JXDatePicker(new Locale("ar", "KW")); JLabel label1 = new JLabel(df.format(1)); JLabel label2 = new JLabel(df.format(2)); JComponent component = new JPanel(); component.add(label1); component.add(label2); component.add(picker); showInFrame(component, "Symbos"); System.out.println(df.format(1)); System.out.println(df.format(2)); } /** * Issue #??-swingx: first week of year in year-week format invalid? * * Same problem for formattedTextField. */ public void interactiveYearWeekFormat() { JComponent box = Box.createVerticalBox(); calendar.set(2009, Calendar.JANUARY, 1); JXDatePicker picker = new JXDatePicker(calendar.getTime()); SimpleDateFormat format = new SimpleDateFormat("yyww"); format.setLenient(false); picker.setFormats(format); picker.getMonthView().setShowingWeekNumber(true); box.add(picker); JFormattedTextField field = new JFormattedTextField(format); field.setValue(picker.getDate()); box.add(field); PropertyChangeListener l = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { LOG.info(evt.getPropertyName() + evt.getSource().getClass().getSimpleName() + evt.getNewValue()); }}; field.addPropertyChangeListener(l); picker.addPropertyChangeListener(l); showInFrame(box, "yearweek format"); } /** * Issue #955-swingx: editor should have same visual props as picker */ public void interactivePickerToEditorVisuals() { JXDatePicker picker = new JXDatePicker(calendar.getTime()); picker.setBackground(Color.RED); picker.setForeground(Color.BLUE); picker.setToolTipText("Colored editor"); JComboBox combo = new JComboBox(new Object[] {"just something", "and something else"}); combo.setEditable(true); combo.setBackground(Color.RED); combo.setForeground(Color.BLUE); combo.setToolTipText("Colored combo editor"); JComponent box = new JXPanel(); box.add(picker); box.add(combo); showInFrame(box, "Colored editor"); } /** * Issue #913-swingx: Datepicker looks bad in some LFs (f.i. Nimbus) * * not only Nimbus ... * - Metal: starts with different height than textfield, shrinks when coming * back from another LF * - motif: button is shrunkenn always * - win: halfway okay, but should have buttons "nearer/integrated" to the field * as spinner, combo does * - Vista: button should visually "merge" into field until rollover (as combo does, * Spinner not * - Can't remember the reason why combo's button isn't re-used? * */ public void interactiveDatePickerNimbus() { JXDatePicker picker = new JXDatePicker(new Date()); JFormattedTextField field = new JFormattedTextField(); field.setValue(picker.getDate()); JSpinner spinner = new JSpinner(new SpinnerDateModel()); spinner.setValue(picker.getDate()); JComboBox box = new JComboBox(new Object[] {picker.getDate()}); box.setEditable(true); JComponent panel = new JXPanel(); panel.add(field); panel.add(picker); panel.add(spinner); panel.add(box); showInFrame(panel, "Nimbus and picker"); } /** * Compare behaviour of notifyAction: DatePicker vs. core components. * * The requirement (from forum) is to trigger the default button on enter. * DatePicker: always eats - BasicDatePickerUI commit action always enabled * JComboBox: always pass-through * JFormattedDateField: pass-through if unchanged, eats if changed. * JTextField: eats if has actionListeners, pass-through if none. */ public void interactiveDialogFocus() { final JComponent content = Box.createVerticalBox(); final JXDatePicker picker = new JXDatePicker(calendar.getTime()); final Action original = picker.getActionMap().get(JXDatePicker.COMMIT_KEY); Action wrapper = new AbstractAction() { public void actionPerformed(ActionEvent e) { original.actionPerformed(e); } @Override public boolean isEnabled() { JFormattedTextField editor = picker.getEditor(); Action action = editor.getActionMap().get(JTextField.notifyAction); return action.isEnabled(); } }; picker.getActionMap().put(JXDatePicker.COMMIT_KEY, wrapper); JComboBox combo = new JComboBox(new Object[] {calendar.getTime()}); combo.setEditable(true); JFormattedTextField field = new JFormattedTextField(); field.setValue(calendar.getTime()); JTextField simple = new JTextField(calendar.getTime().toString()); ActionListener l = new ActionListener() { public void actionPerformed(ActionEvent e) { LOG.info("action from fields: " + e.getSource().getClass()); } }; combo.addActionListener(l); field.addActionListener(l); simple.addActionListener(l); content.add(picker); content.add(combo); content.add(field); content.add(simple); Action open = new AbstractAction("open") { public void actionPerformed(ActionEvent e) { JXDialog dialog = new JXDialog(content); dialog.pack(); dialog.setVisible(true); } }; showInFrame(new JButton(open), "dialog close"); } /** * Issue #709-swingx: DatePicker should have empty constructor which doesn't select. * * Plus deprecate constructors with long - replace by Date parameters. * Deprecate other methods taking long. */ public void interactiveNullDate() { JComponent comp = Box.createVerticalBox(); comp.add(new JLabel("setDate(null)")); JXDatePicker picker = new JXDatePicker(); comp.add(picker); comp.add(new JTextField("initial -1")); showInFrame(comp, "null date"); } /** * Issue #606-swingx: keybindings in monthView popup not working * in InternalFrame. * * Looks like a problem with componentInputMaps in internalFrame: * the registered keys are eaten somewhere (f.i. bind f1 for left is okay) * */ public void interactiveKeyBindingInternalFrame() { JXDatePicker picker = new JXDatePicker(); JInternalFrame iFrame = new JInternalFrame("iFrame", true, true, true, true) { @Override protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) { // hook for debugging return super.processKeyBinding(ks, e, condition, pressed); } }; JComponent box = Box.createVerticalBox(); box.add(picker); box.add(new JLabel("where????")); iFrame.getContentPane().add(box); iFrame.pack(); iFrame.setVisible(true); JDesktopPane desktop = new JDesktopPane() { @Override protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) { // hook for debugging return super.processKeyBinding(ks, e, condition, pressed); } }; desktop.add(iFrame); JInternalFrame other = new JInternalFrame("other", true, true, true, true); other.add(new JLabel("Dummy .... we want it!")); other.pack(); other.setVisible(true); desktop.add(other); JXFrame frame = wrapInFrame(desktop, "InternalFrame keybinding"); frame.setSize(400, 300); frame.setVisible(true); } /** * Issue #725-swingx: review linkPanel/linkDate requirements. * * link panel commit gesture (keystroke F5, double-click) must * commit the date. * * Update the linkDate must * - show in the LinkPanel. * - open the popup in the month of the link date? * */ public void interactiveCommitLinkPanelAction() { final JXDatePicker picker = new JXDatePicker(); ActionListener l = new ActionListener() { public void actionPerformed(ActionEvent e) { LOG.info("received: " + e + "\n adjusting must be false: " + picker.getMonthView().getSelectionModel().isAdjusting()); } }; picker.addActionListener(l); JXFrame frame = showInFrame(picker, "double-click on linkpanel must commit"); Action nextDate = new AbstractAction("change linkdate") { public void actionPerformed(ActionEvent e) { setLinkDateNextMonth(picker); } }; addAction(frame, nextDate); frame.pack(); } /** * Issue #572-swingx: monthView must show linkDate on empty selection. * * add month to linkDate, popup must show month containing link date. * Note: client code using the link date for something different than today * must take care to update the message format (PENDING: is that possible?) */ public void interactiveLinkDate() { final JXDatePicker picker = new JXDatePicker(); setLinkDateNextMonth(picker); Action action = new AbstractAction("next linkdate month") { public void actionPerformed(ActionEvent e) { setLinkDateNextMonth(picker); } }; JXFrame frame = wrapInFrame(picker, "show linkdate if unselected"); addAction(frame, action); addMessage(frame, "incr linkDate and open popup: must show new linkMonth"); frame.pack(); frame.setVisible(true); } protected void setLinkDateNextMonth(final JXDatePicker picker) { Date linkDate = picker.getLinkDay(); // add a months and set as new link date calendar.setTime(linkDate); calendar.add(Calendar.MONTH, 1); picker.setLinkDay(calendar.getTime()); } /** * Issue #565-swingx: occasionally, the popup isn't closed. * to reproduce: open the picker's popup then click into * the comboBox. All is well if click into the textfield. * */ public void interactiveClosePopup() { final JXDatePicker picker = new JXDatePicker(); JComboBox box = new JComboBox(new String[] {"one", "twos"}); box.setEditable(true); ActionListener l = new ActionListener() { public void actionPerformed(ActionEvent e) { LOG.info("received: " + e + "\n and not adjusting: " + picker.getMonthView().getSelectionModel().isAdjusting()); } }; picker.addActionListener(l); box.addActionListener(l); JComponent panel = new JPanel(); panel.add(picker); panel.add(box); panel.add(new JTextField("textfield - click into with open popup")); JXFrame frame = showInFrame(panel, "closed?"); // check if it's a lightweight vs. heavyweight problem frame.setSize(new Dimension(frame.getSize().width, 400)); } /** * compare JFormattedTextField and JXDatePicker pref. * date is slightly cut. Looks like an issue * of the formattedTextField. */ public void interactivePrefSize() { JXDatePicker picker = new JXDatePicker(new Date()); JFormattedTextField field = new JFormattedTextField(new DatePickerFormatter()); field.setValue(picker.getDate()); JComponent panel = new JPanel(); panel.add(picker); panel.add(field); showInFrame(panel, "compare pref width"); } /** * visual testing of selection constraints: upper/lower bounds. * * Issue #567-swingx: * clicking into a unselectable in the popup clears the * selection - should revert to the last valid selection. * PENDING: better control the bounds ... */ public void interactiveBoundsDatePickerClickUnselectable() { JXDatePicker picker = new JXDatePicker(); calendar.add(Calendar.DAY_OF_MONTH, 10); picker.getMonthView().setUpperBound(calendar.getTime()); calendar.add(Calendar.DAY_OF_MONTH, - 20); picker.getMonthView().setLowerBound(calendar.getTime()); showInFrame(picker, "click unselectable clears date"); } /** * Sanity during fix #705-swingx: JXMonthView must not scroll in layoutContainer. * * Here we have a selected date way into the future, to be sure it had to scroll. * Open question: should the monthView be scrolled to the selected always on open or * remember the last month shown * * To reproduce * - open popup with toolbar button * - click next month * - close (without commit/cancel) * - open popup again with toolbar button * - ?? on old navigated or current picker date? */ public void interactiveVisibleMonth() { calendar.add(Calendar.YEAR, 1); final JXDatePicker picker = new JXDatePicker(calendar.getTime()); JXFrame frame = wrapInFrame(picker, "sanity - monthview shows selected"); Action toggleWrapper = new AbstractAction("open popup") { public void actionPerformed(ActionEvent e) { Action togglePopup = picker.getActionMap().get("TOGGLE_POPUP"); togglePopup.actionPerformed(null); CalendarUtils.startOfMonth(calendar); assertEquals(calendar.getTime(), picker.getMonthView().getFirstDisplayedDay()); } }; addAction(frame, toggleWrapper); frame.pack(); frame.setVisible(true); } //-------------------- unit tests /** * Issue #1551-swingx: column setting lost in updateUI */ @Test public void testSynchColumsPicker() { JXDatePicker picker = new JXDatePicker(); int columns = 17; picker.getEditor().setColumns(columns); picker.updateUI(); assertEquals("columns after updateUI", columns, picker.getEditor().getColumns()); } /** * Issue #1551-swingx: column setting lost in updateUI * * Compare JCombobBox behaviour */ @Test public void testSynchColumsCombo() { JComboBox combo = new JComboBox(); combo.setEditable(true); int columns = 17; ((JTextField) combo.getEditor().getEditorComponent()).setColumns(columns); combo.updateUI(); assertEquals("columns after updateUI", columns, ((JTextField) combo.getEditor().getEditorComponent()).getColumns()); } /** * Issue #1551-swingx: column setting lost in updateUI * * Sanity: columns kept in custom editor */ @Test public void testSynchColumsCustomEditorPicker() { JXDatePicker picker = new JXDatePicker(); JFormattedTextField field = new JFormattedTextField(); picker.setEditor(field); int columns = 17; picker.getEditor().setColumns(columns); picker.updateUI(); assertEquals("columns after updateUI", columns, picker.getEditor().getColumns()); } /** * Issue #??-swingx: font setting lost in updateUI */ @Test public void testSynchFontEditorPicker() { JXDatePicker picker = new JXDatePicker(); Font derived = picker.getFont().deriveFont(40f); assertFalse("sanity: derived font must not be uiresource", (derived instanceof UIResource)); picker.setFont(derived); picker.updateUI(); assertEquals("font after updateUI", derived, picker.getFont()); } /** * Issue #??-swingx: font setting lost in updateUI * * Sanity test: font of custom editor kept. */ @Test public void testSynchFontCustomEditorPicker() { JXDatePicker picker = new JXDatePicker(); JFormattedTextField field = new JFormattedTextField(); picker.setEditor(field); Font derived = picker.getFont().deriveFont(40f); assertFalse("sanity: derived font must not be uiresource", (derived instanceof UIResource)); picker.setFont(derived); picker.updateUI(); assertEquals("font after updateUI", derived, picker.getFont()); } /** * Issue #955-swingx: editor should have same visual props as picker * for comparison: combobox. */ @Test public void testSynchEditorFontInitialCombo() { JComboBox combo = new JComboBox(); // make sure we have an editor combo.setEditable(true); assertEquals("combo fonts initially synched", combo.getEditor().getEditorComponent().getFont(), combo.getFont()); } /** * Issue #955-swingx: editor should have same visual props as picker * for comparison: combobox. */ @Test public void testSynchEditorFontCombo() { JComboBox combo = new JComboBox(); // make sure we have an editor combo.setEditable(true); Font font = combo.getFont(); Font derived = font.deriveFont(40f); combo.setFont(derived); assertEquals("combo fonts synched after setting", combo.getEditor().getEditorComponent().getFont(), combo.getFont()); combo.updateUI(); assertEquals("combo fonts synched after updateUI", combo.getEditor().getEditorComponent().getFont(), combo.getFont()); } /** * Issue #955-swingx: editor should have same visual props as picker */ @Test public void testSynchEditorColorsInitialPicker() { JXDatePicker picker = new JXDatePicker(); assertEquals(picker.getForeground(), picker.getEditor().getForeground()); assertEquals(picker.getBackground(), picker.getEditor().getBackground()); } /** * Issue #955-swingx: editor should have same visual props as picker * for comparison: combobox. */ @Test public void testSynchEditorColorsInitialCombo() { JComboBox picker = new JComboBox(); // make sure we have an editor picker.setEditable(true); assertEquals(picker.getForeground(), picker.getEditor().getEditorComponent().getForeground()); assertEquals(picker.getBackground(), picker.getEditor().getEditorComponent().getBackground()); } /** * Issue #955-swingx: editor should have same visual props as picker */ @Test public void testSynchEditorColorsPicker() { JXDatePicker picker = new JXDatePicker(); picker.setBackground(Color.RED); picker.setForeground(Color.BLUE); assertEquals(picker.getForeground(), picker.getEditor().getForeground()); assertEquals(picker.getBackground(), picker.getEditor().getBackground()); } /** * Issue #955-swingx: editor should have same visual props as picker * for comparison: combobox. */ @Test public void testSynchEditorColorsCombo() { JComboBox picker = new JComboBox(); // make sure we have an editor picker.setEditable(true); picker.setBackground(Color.RED); picker.setForeground(Color.BLUE); assertEquals(picker.getForeground(), picker.getEditor().getEditorComponent().getForeground()); assertEquals(picker.getBackground(), picker.getEditor().getEditorComponent().getBackground()); } /** * Issue #764-swingx: picker prefsize too narrow */ public void testPrefSizeGerman() { // wednesday - has width problems calendar.set(2008, Calendar.FEBRUARY, 20); JXDatePicker nullDate = new JXDatePicker(Locale.GERMAN); JXDatePicker notNull = new JXDatePicker(calendar.getTime(), Locale.GERMAN); assertEquals(notNull.getPreferredSize(), nullDate.getPreferredSize()); } /** * Issue #764-swingx: picker prefsize too narrow */ public void testPrefSizeUS() { // wednesday - has width problems calendar.set(2008, Calendar.FEBRUARY, 20); JXDatePicker nullDate = new JXDatePicker(Locale.US); JXDatePicker notNull = new JXDatePicker(calendar.getTime(), Locale.US); assertEquals(notNull.getPreferredSize(), nullDate.getPreferredSize()); } /** * Issue #764-swingx: picker prefsize too narrow */ public void testPrefSizeUSEditor() { // wednesday - has width problems calendar.set(2008, Calendar.FEBRUARY, 20); JXDatePicker nullDate = new JXDatePicker(Locale.US); JXDatePicker notNull = new JXDatePicker(calendar.getTime(), Locale.US); assertEquals(notNull.getEditor().getPreferredSize(), nullDate.getEditor().getPreferredSize()); } // DateFormat longFormat = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL); /** * Issue #572-swingx: monthView must show linkDate on empty selection. * * Definition of picker.linkDate vs. monthView.todayInMillis missing. * PENDING: back out - say linkDate == today, not mutable by client code * but fixed to system? */ public void testLinkDate() { JXDatePicker picker = new JXDatePicker(); Date today = picker.getLinkDay(); Date firstDisplayedDate = picker.getMonthView().getFirstDisplayedDay(); assertSameMonth(today, firstDisplayedDate); calendar.setTime(today); calendar.add(Calendar.MONTH, 2); picker.setLinkDay(calendar.getTime()); assertSameMonth(calendar.getTime(), picker.getMonthView().getFirstDisplayedDay()); } /** * @param linkDate * @param firstDisplayedDate */ private void assertSameMonth(Date linkDate, Date firstDisplayedDate) { calendar.setTime(linkDate); int linkMonth = calendar.get(Calendar.MONTH); calendar.setTime(firstDisplayedDate); assertEquals(linkMonth, calendar.get(Calendar.MONTH)); } /** * Returns a timezone different from the given. * @param defaultZone * @return */ @SuppressWarnings("unused") private TimeZone getSafeAlternativeTimeZone(TimeZone defaultZone) { TimeZone alternative = TimeZone.getTimeZone("GMT-6"); // sanity assertNotNull(alternative); if (alternative.equals(defaultZone)) { alternative = TimeZone.getTimeZone("GMT-7"); // paranoid ... but shit happens assertNotNull(alternative); assertFalse(alternative.equals(defaultZone)); } return alternative; } private Calendar calendar; @Override protected void setUp() throws Exception { calendar = Calendar.getInstance(); } }