/*
* $Id$
*
* Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
* Santa Clara, California 95054, U.S.A. All rights reserved.
*/
package org.jdesktop.swingx.table;
import java.awt.Component;
import java.awt.GraphicsEnvironment;
import java.awt.event.ActionEvent;
import java.util.logging.Logger;
import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.JToggleButton;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import org.jdesktop.swingx.InteractiveTestCase;
import org.jdesktop.swingx.JXTable;
import org.jdesktop.swingx.action.AbstractActionExt;
import org.jdesktop.swingx.action.ActionContainerFactory;
import org.jdesktop.swingx.icon.EmptyIcon;
import org.jdesktop.swingx.plaf.ColumnControlButtonAddon;
import org.jdesktop.swingx.plaf.LookAndFeelAddons;
import org.jdesktop.swingx.table.ColumnControlButton.ColumnVisibilityAction;
import org.jdesktop.swingx.table.ColumnControlButton.DefaultColumnControlPopup;
import org.jdesktop.swingx.table.ColumnControlButtonVisualCheck.GroupKeyActionGrouper;
import org.jdesktop.test.AncientSwingTeam;
import org.jdesktop.test.PropertyChangeReport;
import org.jdesktop.test.TestUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import static org.junit.Assert.*;
/**
* @author Jeanette Winzenburg
*/
@RunWith(JUnit4.class)
public class ColumnControlButtonTest extends InteractiveTestCase {
private static final Logger LOG = Logger
.getLogger(ColumnControlButtonTest.class.getName());
protected TableModel sortableTableModel;
@Before
public void setUpJ4() throws Exception {
setUp();
}
@After
public void tearDownJ4() throws Exception {
tearDown();
}
/**
* Issue: add support for custom grouping of additional actions
* http://java.net/jira/browse/SWINGX-968
*/
@Test
public void testAdditonalActionGrouping() {
JXTable table = new JXTable(10, 4);
AbstractActionExt custom = new AbstractActionExt("Custom") {
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
}
};
custom.putValue(GroupKeyActionGrouper.GROUP_KEY, 0);
table.getActionMap().put(ColumnControlButton.COLUMN_CONTROL_MARKER + "myCommand", custom);
ColumnControlButton button = new ColumnControlButton(table);
button.setActionGrouper(new GroupKeyActionGrouper());
DefaultColumnControlPopup popup = (DefaultColumnControlPopup) button.getColumnControlPopup();
assertEquals("additional actions visible, component count expected ",
table.getColumnCount()
+ 1 /* separator */ + 3 /*default actions with column. prefix*/
+ 1 /* separator custom group */ + 1 /* custom action */,
popup.getPopupMenu().getComponentCount());
}
/**
* Issue http://java.net/jira/browse/SWINGX-1466: add control to show/hide
* additional actions
*/
@Test
public void testAdditionalActionVisible() {
JXTable table = new JXTable(10, 4);
ColumnControlButton button = new ColumnControlButton(table);
assertEquals(true, button.getAdditionalActionsVisible());
DefaultColumnControlPopup popup = (DefaultColumnControlPopup) button.getColumnControlPopup();
assertEquals("additional actions visible, component count expected ",
table.getColumnCount() + 1 /* separator */ + 3 /* actions with column. prefix*/,
popup.getPopupMenu().getComponentCount());
PropertyChangeReport report = new PropertyChangeReport(button);
button.setAdditionalActionsVisible(false);
TestUtils.assertPropertyChangeEvent(report, "additionalActionsVisible", true, false);
assertEquals(false, button.getAdditionalActionsVisible());
assertEquals("additional actions hidden, component count expected ",
table.getColumnCount(),
popup.getPopupMenu().getComponentCount());
}
/**
* Issue #1573-swingx: !hideable column action must be disabled
* Problem: isn't initially
*/
@Test
public void testColumnVisibilityActionOnHideableInitial() {
JXTable table = new JXTable(10, 3);
// set the first column !hideable
table.getColumnExt(0).setHideable(false);
table.setColumnControlVisible(true);
ColumnControlButton columnControl = (ColumnControlButton) table.getColumnControl();
ColumnVisibilityAction action = columnControl.getColumnVisibilityActions().get(0);
assertFalse("action must be disabled initially", action.isEnabled());
}
@Test
public void testColumnVisibilityActionOnHideable() {
JXTable table = new JXTable(10, 3);
table.setColumnControlVisible(true);
ColumnControlButton columnControl = (ColumnControlButton) table.getColumnControl();
ColumnVisibilityAction action = columnControl.getColumnVisibilityActions().get(0);
TableColumnExt columnExt = table.getColumnExt(0);
// visible property is false
columnExt.setVisible(false);
columnExt.setHideable(false);
assertTrue("visibility action must be selected if not hideable", action.isSelected());
assertFalse("action must be disabled", action.isEnabled());
columnExt.setHideable(true);
assertFalse("visibility action must be unselected if hideable", action.isSelected());
}
/**
* Issue #404-swingx: load column control margin from ui.
* Test that column control configures itself with the icon from the ui.
*/
@Test
public void testColumnControlInitialUpdateInsetsUIResource() {
ColumnControlButton control = new ColumnControlButton(new JXTable());
// PENDING JW: why not same? insets can be shared - or not?
// probably setMargin interferes - is doing some things ...
assertEquals("columnControl must have margin from ui",
UIManager.getInsets(ColumnControlButton.COLUMN_CONTROL_BUTTON_MARGIN_KEY),
control.getMargin());
}
/**
* Issue #404-swingx: load column control margin from ui.
* Test that column control loads the margin.
*/
@SuppressWarnings("unused")
@Test
public void testColumnControlLoadsMargin() {
// force loading by instantiating a column control
ColumnControlButton control = new ColumnControlButton(new JXTable());
assertNotNull("columnControl must load lf-specific icon",
UIManager.getInsets(ColumnControlButton.COLUMN_CONTROL_BUTTON_MARGIN_KEY));
}
/**
* Issue #404-swingx: load column control margin from ui.
* Test that addon loads the margin.
*/
@Test
public void testColumnControlAddonLoadsMargin() {
// direct loading of addon
LookAndFeelAddons.contribute(new ColumnControlButtonAddon());
assertNotNull("addon must load lf-specific icon",
UIManager.getInsets(ColumnControlButton.COLUMN_CONTROL_BUTTON_MARGIN_KEY));
}
/**
* Issue #404-swingx: load column control icon from ui.
* Test that table instantiates the column control with ui icon.
*/
@Test
public void testColumnControlInXTable() {
JXTable table = new JXTable();
ColumnControlButton control = (ColumnControlButton) table.getColumnControl();
assertSame("columnControl must have icon from ui",
UIManager.getIcon(ColumnControlButton.COLUMN_CONTROL_BUTTON_ICON_KEY),
control.getIcon());
}
/**
* Issue #404-swingx: load column control icon from ui.
* Test that icon is not updated on updateUI if not uiResource
*/
@Test
public void testColumnControlIconNotUpdateNonActionUIResource() {
ColumnControlButton control = new ColumnControlButton(new JXTable(), new EmptyIcon());
Icon icon = control.getIcon();
String lf = UIManager.getLookAndFeel().getName();
setSystemLF(!defaultToSystemLF);
if (lf.equals(UIManager.getLookAndFeel().getName())) {
LOG.info("cannot run layoutOnLFChange - equal LF" + lf);
return;
}
SwingUtilities.updateComponentTreeUI(control);
assertSame("icon must not be updated on LF change if not UIResource: ",
icon, control.getIcon());
}
/**
* Issue #404-swingx: load column control icon from ui.
* Test that is updated on updateUI
*/
@Test
public void testColumnControlIconUpdateActionUIResource() {
ColumnControlButton control = new ColumnControlButton(new JXTable());
Icon icon = control.getIcon();
String lf = UIManager.getLookAndFeel().getName();
setSystemLF(!defaultToSystemLF);
if (lf.equals(UIManager.getLookAndFeel().getName())) {
LOG.info("cannot run layoutOnLFChange - equal LF" + lf);
return;
}
SwingUtilities.updateComponentTreeUI(control);
assertNotSame("sanity: ui did reload icon: ", icon,
UIManager.getIcon(ColumnControlButton.COLUMN_CONTROL_BUTTON_ICON_KEY));
assertNotSame("icon must be updated on LF change: ", icon, control.getIcon());
}
/**
* Issue #404-swingx: load column control icon from ui.
* Test that column control configures itself with the icon from the ui.
*/
@Test
public void testColumnControlInitialUpdateActionUIResource() {
ColumnControlButton control = new ColumnControlButton(new JXTable());
assertSame("columnControl must have icon from ui",
UIManager.getIcon(ColumnControlButton.COLUMN_CONTROL_BUTTON_ICON_KEY),
control.getIcon());
}
/**
* Issue #404-swingx: load column control icon from ui.
* Test that column control loads the icon.
*/
@SuppressWarnings("unused")
@Test
public void testColumnControlLoadsIcon() {
// force loading by instantiating a column control
ColumnControlButton control = new ColumnControlButton(new JXTable());
assertNotNull("columnControl must load lf-specific icon",
UIManager.getIcon(ColumnControlButton.COLUMN_CONTROL_BUTTON_ICON_KEY));
}
/**
* Issue #404-swingx: load column control icon from ui.
* Test that addon loads the icon.
*/
@Test
public void testColumnControlAddonLoadsIcon() {
// direct loading of addon
LookAndFeelAddons.contribute(new ColumnControlButtonAddon());
assertNotNull("addon must load lf-specific icon",
UIManager.getIcon(ColumnControlButton.COLUMN_CONTROL_BUTTON_ICON_KEY));
}
/**
* Issue #429-swingx: ClassCastException if column identifiers are not
* String type.
*
*/
@Test
public void testNonStringIdentifier() {
JXTable table = new JXTable(0, 2);
table.getColumn(0).setIdentifier(new Object());
table.setColumnControlVisible(true);
table.getColumnControl();
}
@Test
public void testNotNullColumnModelListener() {
JXTable table = new JXTable(0, 2);
table.setColumnControlVisible(true);
assertNotNull(((ColumnControlButton)table.getColumnControl()).columnModelListener);
}
/**
* Tests if subclasses are allowed to not create a visibility action.
* This might happen if they want to exempt certain columns from
* user interaction.
*
*/
@Test
public void testNullVisibilityAction() {
JXTable table = new JXTable();
ColumnControlButton columnControl = new ColumnControlButton(table) {
@Override
protected ColumnVisibilityAction createColumnVisibilityAction(TableColumn column) {
if (column.getModelIndex() == 0) return null;
return super.createColumnVisibilityAction(column);
}
};
table.setColumnControl(columnControl);
table.setColumnControlVisible(true);
table.setModel(sortableTableModel);
}
/**
* test that the actions synch's its own selected property with
* the column's visible property. <p>
*
* Looks as if the non-synch of action.setSelected only shows
* if the ColumnControlPopup doesn't create a menuitem via ActionFactory: the
* listeners internally installed via ActionFactory probably take care?
* <p>
*
* An analogous test in the incubator (in kleopatra/.../table) did fail
* for a dialog based custom ColumnControlPopup. For now, changed the visibility
* action to explicitly update the tableColumn. All tests are passing,
* but need to further evaluate.
*
*/
@Test
public void testColumnVisibilityAction() {
JXTable table = new JXTable(10, 3);
table.setColumnControlVisible(true);
ColumnControlButton columnControl = (ColumnControlButton) table.getColumnControl();
ColumnVisibilityAction action = columnControl.getColumnVisibilityActions().get(0);
TableColumnExt columnExt = table.getColumnExt(0);
boolean visible = columnExt.isVisible();
// sanity
assertTrue(visible);
assertEquals(columnExt.isVisible(), action.isSelected());
action.setSelected(!visible);
// hmmm... here it's working? unexpected
// synch might be done by the listener's installed by ActionFactor.createMenuItem()?
assertEquals(!visible, columnExt.isVisible());
}
/**
* Tests that enabled property of table and column control is synched dynamically.
*/
@Test
public void testDynamicDisabled() {
JXTable table = new JXTable(10, 3);
table.setColumnControlVisible(true);
assertEquals(table.isEnabled(), table.getColumnControl().isEnabled());
table.setEnabled(!table.isEnabled());
assertEquals(table.isEnabled(), table.getColumnControl().isEnabled());
}
/**
* suspected: enabled not synched on init.
* But is (done in ccb.installTable()).
*
*/
@Test
public void testInitialDisabled() {
JXTable table = new JXTable(10, 3);
table.setEnabled(false);
table.setColumnControlVisible(true);
assertEquals(table.isEnabled(), table.getColumnControl().isEnabled());
}
/**
* guarantee that at least one column is always visible.
*
*/
@Test
public void testMinimumColumnCountOne() {
JXTable table = new JXTable(10, 2);
table.setColumnControlVisible(true);
table.getColumnExt(0).setVisible(false);
assertEquals(1, table.getColumnCount());
}
/**
* Issue #229-swingx: increasing listener list in column actions.
*
*/
@Test
public void testActionListenerCount() {
JXTable table = new JXTable(10, 1);
Action action = table.getActionMap().get(JXTable.HORIZONTALSCROLL_ACTION_COMMAND);
if (!(action instanceof AbstractActionExt)) {
LOG.info("cannot run testColumnActionListenerCount - action not of type AbstractAction");
return;
}
AbstractActionExt extAction = (AbstractActionExt) action;
assertTrue(extAction.isStateAction());
assertEquals(0, extAction.getPropertyChangeListeners().length);
AbstractButton menuItem = new JCheckBoxMenuItem();
ActionContainerFactory factory = new ActionContainerFactory(null);
factory.configureSelectableButton(menuItem, extAction, null);
// sanity: here the action is bound to a menu item in the columnControl
// should have one ad
int initialPCLCount = extAction.getPropertyChangeListeners().length;
// sanity: expect it to be 2 - one is the menuitem itself, another
// the TogglePCL registered by the ActionContainerFacory
assertEquals(2, initialPCLCount);
menuItem = new JToggleButton();
factory.configureSelectableButton(menuItem, extAction, null);
// 2 menuitems are listening
assertEquals(2* initialPCLCount, extAction.getPropertyChangeListeners().length);
}
/**
* Issue #153-swingx: ClassCastException if actionMap key is not a string.
*
*/
@Test
public void testNonStringActionKeys() {
JXTable table = new JXTable();
Action l = new AbstractAction("dummy") {
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
}
};
table.registerKeyboardAction(l , KeyStroke.getKeyStroke("ESCAPE"), JComponent.WHEN_FOCUSED);
table.setColumnControlVisible(true);
table.getColumnControl();
}
@Test
public void testColumnControlReleaseAction() {
final JXTable table = new JXTable(sortableTableModel);
final TableColumnExt priorityColumn = table.getColumnExt("First Name");
int listenerCount = priorityColumn.getPropertyChangeListeners().length;
table.setColumnControlVisible(true);
// JW: the columnControlButton is created lazily, so we
// have to access to test if listeners are registered.
table.getColumnControl();
assertEquals("numbers of listeners must be increased", listenerCount + 1,
priorityColumn.getPropertyChangeListeners().length);
int totalColumnCount = table.getColumnCount();
table.removeColumn(priorityColumn);
assertEquals("number of columns reduced", totalColumnCount - 1, table.getColumnCount());
assertEquals("all listeners must be removed", 0,
priorityColumn.getPropertyChangeListeners().length);
}
/**
* Issue #212-swingx:
*
* Behaviour change:
* <ul>
* <li>before - guarantee that exactly one column is always visible,
* independent of source of visibiblity change
* <li> now - this is true for user gesture induced invisible (through
* columnControl, but not for programmatic hiding. It's up to
* developers to not hide all. To alleviate the effects if they
* hide all, the JXTableHeader and ColumnControl is always visible.
* </ul>
* This is testing the old behaviour (guarding against regression)
* Here we directly set the second last visible column to invisible. This
* failed if a) column visibility is set after adding the table to a frame
* and b) model.count = 2.
*
*/
@Test
public void testSetAllColumnsToInvisible() {
// This test will not work in a headless configuration.
if (GraphicsEnvironment.isHeadless()) {
return;
}
final JXTable table = new JXTable(10, 2);
table.setColumnControlVisible(true);
wrapWithScrollingInFrame(table, "");
table.getColumnExt(0).setVisible(false);
assertEquals(1, table.getColumnCount());
table.getColumnExt(0).setVisible(false);
assertEquals(0, table.getColumnCount());
}
/**
* Issue #212-swingx:
*
* guarantee that exactly one column is always visible if
* visibility is toggled via the ColumnControl.
*
* Here we deselect the menuitem.
*
*/
@Test
public void testSetLastColumnMenuItemToUnselected() {
// This test will not work in a headless configuration.
if (GraphicsEnvironment.isHeadless()) {
return;
}
final JXTable table = new JXTable(10, 1);
table.setColumnControlVisible(true);
wrapWithScrollingInFrame(table, "");
ColumnControlButton columnControl = (ColumnControlButton) table.getColumnControl();
Component[] items = ((DefaultColumnControlPopup) columnControl.getColumnControlPopup()).getPopupMenu().getComponents();
((JMenuItem) items[0]).setSelected(false);
assertEquals(1, table.getColumnCount());
}
/**
* Issue #192: initially invisibility columns are hidden
* but marked as visible in control.
*
* Issue #38 (swingx): initially invisble columns don't show up
* in the column control list.
*
*
*/
@Test
public void testColumnControlInvisibleColumns() {
final JXTable table = new JXTable(sortableTableModel);
// columns set to invisible before setting the columnControl
// will not be inserted into the column control's list
// table.getColumnExt("Last Name").setVisible(false);
table.setColumnControlVisible(true);
int totalColumnCount = table.getColumnCount();
final TableColumnExt priorityColumn = table.getColumnExt("First Name");
priorityColumn.setVisible(false);
ColumnControlButton columnControl = (ColumnControlButton) table.getColumnControl();
assertNotNull("popup menu not null", columnControl.popup);
int columnMenuItems = 0;
Component[] items = ((DefaultColumnControlPopup) columnControl.getColumnControlPopup()).getPopupMenu().getComponents();
for (int i = 0; i < items.length; i++) {
if (!(items[i] instanceof JMenuItem)) {
break;
}
columnMenuItems++;
}
// wrong assumption - has separator and actions!
assertEquals("menu items must be equal to columns", totalColumnCount,
columnMenuItems);
JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) ((DefaultColumnControlPopup) columnControl.getColumnControlPopup()).getPopupMenu()
.getComponent(0);
// sanit assert
assertEquals(priorityColumn.getHeaderValue(), menuItem.getText());
assertEquals("selection of menu must be equal to column visibility",
priorityColumn.isVisible(), menuItem.isSelected());
}
public ColumnControlButtonTest() {
super("ColumnControlButtonTest");
}
// flag used in setup to explicitly choose LF
private boolean defaultToSystemLF;
@Override
protected void setUp() throws Exception {
super.setUp();
// make sure we have the same default for each test
defaultToSystemLF = false;
setSystemLF(defaultToSystemLF);
sortableTableModel = new AncientSwingTeam();
}
public static void main(String args[]) {
setSystemLF(false);
ColumnControlButtonTest test = new ColumnControlButtonTest();
try {
test.runInteractiveTests();
// test.runInteractiveTests("interactive.*Column.*");
// test.runInteractiveTests("interactive.*TableHeader.*");
// test.runInteractiveTests("interactive.*SorterP.*");
// test.runInteractiveTests("interactive.*Column.*");
} catch (Exception e) {
System.err.println("exception when executing interactive tests:");
e.printStackTrace();
}
}
}