/* * $Id$ * * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, * Santa Clara, California 95054, U.S.A. All rights reserved. */ package org.jdesktop.swingx; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.KeyboardFocusManager; import java.awt.Point; import java.awt.event.ActionEvent; import java.util.ArrayList; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.Random; import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.AbstractButton; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JToolBar; import javax.swing.JViewport; import javax.swing.KeyStroke; import javax.swing.RowFilter; import javax.swing.RowSorter.SortKey; import javax.swing.ScrollPaneLayout; import javax.swing.SortOrder; import javax.swing.SwingUtilities; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import javax.swing.event.RowSorterEvent; import javax.swing.event.RowSorterEvent.Type; import javax.swing.event.RowSorterListener; import javax.swing.event.TableColumnModelEvent; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableModel; import org.jdesktop.swingx.action.AbstractActionExt; import org.jdesktop.swingx.decorator.AbstractHighlighter; import org.jdesktop.swingx.decorator.ColorHighlighter; import org.jdesktop.swingx.decorator.ComponentAdapter; import org.jdesktop.swingx.decorator.HighlightPredicate; import org.jdesktop.swingx.decorator.Highlighter; import org.jdesktop.swingx.decorator.HighlighterFactory; import org.jdesktop.swingx.hyperlink.AbstractHyperlinkAction; import org.jdesktop.swingx.hyperlink.LinkModel; import org.jdesktop.swingx.hyperlink.LinkModelAction; import org.jdesktop.swingx.renderer.CheckBoxProvider; import org.jdesktop.swingx.renderer.DefaultTableRenderer; import org.jdesktop.swingx.renderer.HyperlinkProvider; import org.jdesktop.swingx.renderer.StringValue; import org.jdesktop.swingx.search.SearchFactory; import org.jdesktop.swingx.sort.DefaultSortController; import org.jdesktop.swingx.sort.SortUtils; import org.jdesktop.swingx.table.ColumnFactory; import org.jdesktop.swingx.table.DatePickerCellEditor; import org.jdesktop.swingx.table.TableColumnExt; import org.jdesktop.swingx.treetable.FileSystemModel; import org.jdesktop.test.AncientSwingTeam; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Split from old JXTableUnitTest - contains "interactive" * methods only. <p> * * PENDING: too many frames to fit all on screen - either split into different * tests or change positioning algo to start on top again if hidden. <p> * @author Jeanette Winzenburg */ @RunWith(JUnit4.class) public class JXTableVisualCheck extends JXTableUnitTest { private static final Logger LOG = Logger.getLogger(JXTableVisualCheck.class .getName()); public static void main(String args[]) { JXTableVisualCheck test = new JXTableVisualCheck(); try { // test.runInteractiveTests(); // test.runInteractiveTests("interactive.*FloatingPoint.*"); // test.runInteractiveTests("interactive.*Disable.*"); // test.runInteractiveTests("interactive.*ColumnControl.*"); // test.runInteractiveTests("interactive.*RowHeight.*"); // test.runInteractiveTests("interactive.*Remove.*"); // test.runInteractiveTests("interactive.*ColumnProp.*"); // test.runInteractiveTests("interactive.*Multiple.*"); // test.runInteractiveTests("interactive.*RToL.*"); // test.runInteractiveTests("interactive.*Scrollable.*"); // test.runInteractiveTests("interactive.*isable.*"); // test.runInteractive("EditorNull"); test.runInteractive("PopupTrigger"); // test.runInteractiveTests("interactive.*Policy.*"); // test.runInteractiveTests("interactive.*Rollover.*"); // test.runInteractiveTests("interactive.*Revalidate.*"); // test.runInteractiveTests("interactive.*UpdateUI.*"); // test.runInteractiveTests("interactiveColumnHighlighting"); } catch (Exception e) { System.err.println("exception when executing interactive tests:"); e.printStackTrace(); } } /** * Issue #1563-swingx: find cell that was clicked for componentPopup * * Example of how to use: * - in actionPerformed * - in popupMenuWillBecomeVisible */ public void interactivePopupTriggerLocation() { JXTable table = new JXTable(new AncientSwingTeam()); table.setCellSelectionEnabled(true); JPopupMenu popup = new JPopupMenu(); Action action = new AbstractAction("cell found in actionPerformed") { @Override public void actionPerformed(ActionEvent e) { JXTable table = SwingXUtilities.getAncestor(JXTable.class, (Component) e.getSource()); Point trigger = table.getPopupTriggerLocation(); Point cell = null; if (trigger != null) { int row = table.rowAtPoint(trigger); int column = table.columnAtPoint(trigger); table.setRowSelectionInterval(row, row); table.setColumnSelectionInterval(column, column); cell = new Point(column, row); } else { table.clearSelection(); } LOG.info("popupTrigger/cell " + trigger + "/" + cell); } }; popup.add(action); final Action onShowing = new AbstractAction("dynamic: ") { @Override public void actionPerformed(ActionEvent e) { LOG.info("" + getValue(NAME)); } }; popup.add(onShowing); PopupMenuListener l = new PopupMenuListener() { @Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) { // doesn't work: popup itself cannot be used as // starting component, bug? // JXTable table = SwingXUtilities.getAncestor(JXTable.class, // (Component) e.getSource()); JXTable table = (JXTable) ((JPopupMenu) e.getSource()).getInvoker(); Point trigger = table.getPopupTriggerLocation(); Point cell = null; if (trigger != null) { int row = table.rowAtPoint(trigger); int column = table.columnAtPoint(trigger); // here we set the cell focus, just to do a bit differently // from the other action table.setRowSelectionInterval(row, row); table.setColumnSelectionInterval(column, column); table.clearSelection(); cell = new Point(column, row); } onShowing.putValue(Action.NAME, "popupTrigger/cell " + trigger + "/" + cell); } @Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { } @Override public void popupMenuCanceled(PopupMenuEvent e) { } }; popup.addPopupMenuListener(l); table.setComponentPopupMenu(popup); showWithScrollingInFrame(table, "PopupTriggerLocation"); } /** * Issue #1535-swingx: GenericEditor fires editingStopped even if * value invalid (= empty). Leads to NPE in columnMoved (jdk7) or when * external code calls the stop/cancel sequence. * Core issue but c&p'd to JXTable. * * * 1. doesn't allow editing if the column type doesn't have a * single parameter constructor of type String * (fails silently in this case) - unrelated to this issue * * 2. throws NPE on moving column while editing, the editingValue * is empty and the class cannot handle empty strings. The NPE * is triggered in column moved, because the GenericEditor * fires twice. http://stackoverflow.com/q/13524519/203657 * Visible only when run with jdk7 because the inappropriate * removeEditor is fixed :-) */ public void interactiveEditorNull() { final JTable table = new JTable(create1535TableModel()); Action action = new AbstractAction("terminateEditing") { @Override public void actionPerformed(ActionEvent e) { if (table.isEditing() && !table.getCellEditor().stopCellEditing() ) { table.getCellEditor().cancelCellEditing(); } } }; JXFrame frame = wrapWithScrollingInFrame(table, "Core: NPE on stopping edit"); addAction(frame, action); show(frame); } public void interactiveEditorNullX() { final JTable table = new JXTable(create1535TableModel()); Action action = new AbstractAction("terminateEditing") { @Override public void actionPerformed(ActionEvent e) { if (table.isEditing() && !table.getCellEditor().stopCellEditing() ) { table.getCellEditor().cancelCellEditing(); } } }; JXFrame frame = wrapWithScrollingInFrame(table, "xTable: NPE on stopping edit"); addAction(frame, action); show(frame); } /** * Issue #1392- swingx: columnControl lost on toggle CO and LAF * * Not easily reproducible - happens * if the scrollPane's layout is re-created on updateUI. */ public void interactiveColumnControlLAF() { JXTable table = new JXTable(new AncientSwingTeam()); table.setColumnControlVisible(true); JScrollPane scrollPane = new JScrollPane(table) { /** * @inherited <p> */ @Override public void updateUI() { super.updateUI(); setLayout(new ScrollPaneLayout()); } }; JTable rowHeader = new JTable(10, 1); scrollPane.setRowHeaderView(rowHeader); JLabel label = new JLabel("rowHeader"); scrollPane.setCorner(JScrollPane.UPPER_LEADING_CORNER, label); JXFrame frame = wrapInFrame(scrollPane, "xTable, coreScrollPane"); addComponentOrientationToggle(frame); show(frame); } /** * Issue #1195-swingx: keep selection on remove * Basically, this is a core issue - fixed in DefaultSortController during * last cleanup round. * */ public void interactiveRemoveAndSelected() { final JXTable table = new JXTable(10, 5); JXFrame frame = wrapWithScrollingInFrame(table, "Issue #1195-swingx - keep selection on remove"); Action action = new AbstractAction("remove row before selected") { @Override public void actionPerformed(ActionEvent e) { int selected = table.getSelectedRow(); if (selected < 1) return; ((DefaultTableModel) table.getModel()).removeRow(selected - 1); } }; addAction(frame, action); show(frame); } /** * Issue #35-swingx: visual indicators of secondary sort columns * * Trick by David Hall: use unicode char * http://forums.java.net/jive/thread.jspa?threadID=71090 * * As he already noted: it's not necessarily pretty looking ;-) */ public void interactiveHeaderSecondarySortIndicator() { DefaultTableModel model = new DefaultTableModel(0, 3) { /** * @inherited <p> */ @Override public Class<?> getColumnClass(int columnIndex) { if (columnIndex < getColumnCount() - 1) { return Integer.class; } return super.getColumnClass(columnIndex); } }; int min = hexToInt("25A0"); int max = hexToInt("25FF"); for (int i = min; i <= max; i++) { Object[] row = new Object[3]; row[0] = i; row[1] = i; row[2] = (char) i + ""; model.addRow(row); } final JXTable table = new JXTable(); ColumnFactory factory = new ColumnFactory() { /** * @inherited <p> */ @Override public TableColumnExt createTableColumn(int modelIndex) { // TODO Auto-generated method stub TableColumnExt tableColumn = new SortAwareTableColumnExt(); tableColumn.setModelIndex(modelIndex); return tableColumn; } }; table.setColumnControlVisible(true); table.setColumnFactory(factory); table.setModel(model); RowSorterListener l = new RowSorterListener() { @SuppressWarnings("unchecked") @Override public void sorterChanged(RowSorterEvent e) { if (e.getType() == Type.SORT_ORDER_CHANGED) { List<TableColumn> list = table.getColumns(true); List<? extends SortKey> sortKeys = new ArrayList<SortKey>(e.getSource().getSortKeys()); // remove primary List<? extends SortKey> secondary = sortKeys.subList(1, sortKeys.size()); for (TableColumn tableColumn : list) { if (tableColumn instanceof TableColumnExt) { SortKey key = SortUtils.getFirstSortKeyForColumn(secondary, tableColumn.getModelIndex()); Object property = null; if (key != null && SortUtils.isSorted(key.getSortOrder())) { property = key.getSortOrder(); } ((TableColumnExt) tableColumn).putClientProperty(SortAwareTableColumnExt.SORT_ORDER_KEY, property); } } } } }; table.getRowSorter().addRowSorterListener(l); StringValue sv = new StringValue() { @Override public String getString(Object value) { return Integer.toHexString(((Integer) value).intValue()); } }; table.getColumn(1).setCellRenderer(new DefaultTableRenderer(sv, JLabel.RIGHT)); JXFrame frame = showWithScrollingInFrame(table, "Geometric shapes"); addComponentOrientationToggle(frame); } public static class SortAwareTableColumnExt extends TableColumnExt { public final static String DESCENDING_CHAR = " \u25bf"; public final static String ASCENDING_CHAR = " \u25b5"; public final static String SORT_ORDER_KEY = "columnExt.SortOrder"; /** * @inherited <p> */ @Override public Object getHeaderValue() { Object header = super.getHeaderValue(); Object sortOrder = getClientProperty(SORT_ORDER_KEY); if (SortOrder.ASCENDING == sortOrder) { header = header + ASCENDING_CHAR; } else if (SortOrder.DESCENDING == sortOrder){ header = header + DESCENDING_CHAR; } return header; } } private int hexToInt(String s) { return Integer.parseInt(s, 16); } /** * Issue #1254-swingx: JXTable not revalidated on update if filter. * * Core JTable issue * Problem is that the update might change the visible row count. */ public void interactiveRevalidateOnUpdateWithFilter() { String data[][] = { { "satuAA", "Satu", "SATU", "1" }, { "duaAAB", "Dua", "DUA", "2" }, { "tigaBAA", "Tiga", "TIGA", "3" }, { "empatBBA", "Empat", "EMPAT", "4" } }; String cols[] = { "col1", "col2", "col3", "col4" }; final JXTable table = new JXTable(data, cols); RowFilter<TableModel, Integer> tm = RowFilter.regexFilter( ".*AA.*", 0); table.setRowFilter(tm); JXFrame frame = wrapWithScrollingInFrame(table, "Update with RowFilter"); Action action = new AbstractAction("filter first row") { boolean hasAA = true; @Override public void actionPerformed(ActionEvent e) { String newValue = hasAA ? "BB" : "AA"; hasAA = !hasAA; table.getModel().setValueAt(newValue, 0, 0); } }; addAction(frame, action); show(frame); } /** * Trying to make null biggest value. * * Can't do - nulls don't reach the comparator. */ public void interactiveSortWithNull() { JXTable table = new JXTable(createAscendingModel(1, 20)); for (int i = 0; i < table.getRowCount(); i+=2) { table.setValueAt(null, i, 0); } Comparator<?> comparator = new Comparator<Object>() { @Override public int compare(Object o1, Object o2) { if (o1 == null) { if (o2 == null) return 0; return 1; } if (o2 == null) { return -1; } return ((Integer) o1).compareTo((Integer) o2); } }; table.getColumnExt(0).setComparator(comparator); showWithScrollingInFrame(table, "nulls"); } /** * Quick check of sort order cycle (including pathologicals) */ public void interactiveSortOrderCycle() { final JXTable table = new JXTable(new AncientSwingTeam()); JXFrame frame = wrapWithScrollingInFrame(table, new JTable(table.getModel()), "sort cycles"); Action three = new AbstractAction("three-cylce") { @Override public void actionPerformed(ActionEvent e) { table.setSortOrderCycle(SortOrder.ASCENDING, SortOrder.DESCENDING, SortOrder.UNSORTED); } }; addAction(frame, three); Action two = new AbstractAction("two-cylce") { @Override public void actionPerformed(ActionEvent e) { table.setSortOrderCycle(SortOrder.ASCENDING, SortOrder.DESCENDING); } }; addAction(frame, two); Action one = new AbstractAction("one-cylce") { @Override public void actionPerformed(ActionEvent e) { table.setSortOrderCycle(SortOrder.DESCENDING); } }; addAction(frame, one); Action none = new AbstractAction("empty-cylce") { @Override public void actionPerformed(ActionEvent e) { table.setSortOrderCycle(); } }; addAction(frame, none); show(frame); } public void interactiveMultiColumnSort() { DefaultTableModel model = createMultiSortModel(); JXTable table = new JXTable(model); table.setVisibleRowCount(model.getRowCount()); JXFrame frame = wrapWithScrollingInFrame(table, "multi-column-sort"); final DefaultSortController<?> rowSorter = (DefaultSortController<?>) table.getRowSorter(); final List<SortKey> sortKeys = new ArrayList<SortKey>(); for (int i = 0; i < rowSorter.getMaxSortKeys(); i++) { sortKeys.add(new SortKey(i, SortOrder.ASCENDING)); } Action setSortKeys = new AbstractAction("sortKeys") { @Override public void actionPerformed(ActionEvent e) { rowSorter.setSortKeys(sortKeys); } }; addAction(frame, setSortKeys); Action reset = new AbstractAction("resetSort") { @Override public void actionPerformed(ActionEvent e) { rowSorter.setSortKeys(null); } }; rowSorter.setSortable(0, false); addAction(frame, reset); show(frame); } /** * @return */ private DefaultTableModel createMultiSortModel() { String[] first = { "animal", "plant" }; String[] second = {"insect", "mammal", "spider" }; String[] third = {"red", "green", "yellow", "blue" }; Integer[] age = { 1, 5, 12, 20, 100 }; Object[][] rows = new Object[][] { first, second, third, age }; DefaultTableModel model = new DefaultTableModel(20, 4) { @Override public Class<?> getColumnClass(int columnIndex) { return columnIndex == getColumnCount() - 1 ? Integer.class : super.getColumnClass(columnIndex); } }; for (int i = 0; i < rows.length; i++) { setValues(model, rows[i], i); } return model; } /** * @param model * @param first * @param i */ private void setValues(DefaultTableModel model, Object[] first, int column) { Random seed = new Random(); for (int row = 0; row < model.getRowCount(); row++) { int random = seed.nextInt(first.length); model.setValueAt(first[random], row, column); } } /** * Issue #908-swingx: move updateUI responsibility into column. * */ public void interactiveUpdateUIEditors() { DefaultTableModel model = new DefaultTableModel(5, 5) { @Override public Class<?> getColumnClass(int columnIndex) { if (getValueAt(0, columnIndex) == null) return super.getColumnClass(columnIndex); return getValueAt(0, columnIndex).getClass(); } }; for (int i = 0; i < model.getRowCount(); i++) { model.setValueAt(new Date(), i, 0); model.setValueAt(true, i, 1); } JXTable table = new JXTable(model); TableCellEditor editor = new DatePickerCellEditor(); table.getColumn(0).setCellEditor(editor); table.getColumn(4).setCellRenderer(new DefaultTableRenderer(new CheckBoxProvider())); showWithScrollingInFrame(table, "toggle ui - must update editors/renderers"); } /** * Issue #550-swingx: xtable must not reset columns' pref/size on * structureChanged if autocreate is false. * * */ public void interactiveColumnWidthOnStructureChanged() { final JXTable table = new JXTable(new AncientSwingTeam()); table.setAutoCreateColumnsFromModel(false); table.setAutoResizeMode(JXTable.AUTO_RESIZE_OFF); table.setColumnControlVisible(true); // min/max is respected // mini.setMaxWidth(5); // mini.setMinWidth(5); Action structureChanged = new AbstractAction("fire structure changed") { @Override public void actionPerformed(ActionEvent e) { table.tableChanged(null); } }; JXFrame frame = showWithScrollingInFrame(table, "structure change must not re-size columns"); addAction(frame, structureChanged); show(frame); } /** * Issue #675-swingx: esc doesn't reach rootpane. * * Verify that the escape is intercepted only if editing. * BUT: (core behaviour) starts editing in table processKeyBinding. So every * second is not passed on. */ public void interactiveDialogCancelOnEscape() { Action cancel = new AbstractActionExt("cancel") { @Override public void actionPerformed(ActionEvent e) { LOG.info("performed: cancel action"); } }; final JButton field = new JButton(cancel); JXTable xTable = new JXTable(10, 3); JTable table = new JTable(xTable.getModel()); JXFrame frame = wrapWithScrollingInFrame(xTable, table, "escape passed to rootpane (if editing)"); frame.setCancelButton(field); frame.add(field, BorderLayout.SOUTH); frame.setVisible(true); } /** * Issue #508/547-swingx: clean up of pref scrollable. * Visual check: column init on model change. * */ public void interactivePrefScrollable() { final DefaultTableModel tableModel = new DefaultTableModel(30, 7); final AncientSwingTeam ancientSwingTeam = new AncientSwingTeam(); final JXTable table = new JXTable(tableModel); table.setColumnControlVisible(true); table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); final JXFrame frame = showWithScrollingInFrame(table, "initial sizing"); addMessage(frame, "initial size: " + table.getPreferredScrollableViewportSize()); Action action = new AbstractActionExt("toggle model") { @Override public void actionPerformed(ActionEvent e) { table.setModel(table.getModel() == tableModel ? ancientSwingTeam : tableModel); frame.pack(); } }; addAction(frame, action); frame.pack(); } /** * Issue #508/547-swingx: clean up of pref scrollable. * Visual check: dynamic logical scroll sizes * Toggle visual row/column count. */ public void interactivePrefScrollableDynamic() { final AncientSwingTeam ancientSwingTeam = new AncientSwingTeam(); final JXTable table = new JXTable(ancientSwingTeam); table.setColumnControlVisible(true); table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); final JXFrame frame = wrapWithScrollingInFrame(table, "Dynamic pref scrollable"); Action action = new AbstractActionExt("vis row") { @Override public void actionPerformed(ActionEvent e) { int visRowCount = table.getVisibleRowCount() + 5; if (visRowCount > 30) { visRowCount = 10; } table.setVisibleRowCount(visRowCount); frame.pack(); } }; addAction(frame, action); Action columnAction = new AbstractActionExt("vis column") { @Override public void actionPerformed(ActionEvent e) { int visColumnCount = table.getVisibleColumnCount(); if (visColumnCount > 8) { visColumnCount = -1; } else if (visColumnCount < 0 ) { visColumnCount = 2; } else { visColumnCount += 2; } table.setVisibleColumnCount(visColumnCount); frame.pack(); } }; addAction(frame, columnAction); frame.setVisible(true); frame.pack(); } /** * Issue #417-swingx: disable default find. * * Possible alternative to introducing disable api as suggested in the * issue report: disable the action? Move the action up the hierarchy to * the parent actionmap? Maybe JX specific parent? * */ public void interactiveDisableFind() { final JXTable table = new JXTable(sortableTableModel); Action findAction = new AbstractActionExt() { @Override public void actionPerformed(ActionEvent e) { SearchFactory.getInstance().showFindDialog(table, table.getSearchable()); } @Override public boolean isEnabled() { return false; } }; table.getActionMap().put("find", findAction); showWithScrollingInFrame(table, "disable finding"); } /** * visually check if we can bind the CCB's action to a keystroke. * * Working, but there's a visual glitch if opened by keystroke: * the popup is not trailing aligned to the CCB. And the * CCB must be visible, otherwise there's an IllegalStateException * because the popup tries to position itself relative to the CCB. * */ public void interactiveKeybindingColumnControl() { JXTable table = new JXTable(sortableTableModel); // JW: currently the CCB must be visible table.setColumnControlVisible(true); Action openCCPopup = ((AbstractButton) table.getColumnControl()).getAction(); String actionKey = "popupColumnControl"; table.getActionMap().put(actionKey, openCCPopup); KeyStroke keyStroke = KeyStroke.getKeyStroke("F2"); table.getInputMap(JXTable.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(keyStroke, actionKey); showWithScrollingInFrame(table, "Press F2 to open column control"); } /** * calculate reasonable table rowHeight withouth "white pixel" in editor. * Compare table and xtable */ public void interactiveCompareRowHeight() { JXTable xtable = new JXTable(sortableTableModel); xtable.setColumnControlVisible(true); xtable.setShowGrid(false, false); JTable table = new JTable(sortableTableModel); table.setShowHorizontalLines(false); table.setShowVerticalLines(false); table.setRowMargin(0); table.getColumnModel().setColumnMargin(0); JXFrame frame = wrapWithScrollingInFrame(xtable, table, "compare default rowheight of xtable vs. table"); frame.setVisible(true); } /** * visually check if terminateEditOnFocusLost, autoStartEdit * work as expected. * */ public void interactiveToggleEditProperties() { final JXTable table = new JXTable(10, 2); JXFrame frame = wrapWithScrollingInFrame(table, new JButton("something to focus"), "JXTable: toggle terminate/autoStart on left (right is dummy) "); Action toggleTerminate = new AbstractAction("toggleTerminate") { @Override public void actionPerformed(ActionEvent e) { table.setTerminateEditOnFocusLost(!table.isTerminateEditOnFocusLost()); } }; addAction(frame, toggleTerminate); Action toggleAutoStart = new AbstractAction("toggleAutoStart") { @Override public void actionPerformed(ActionEvent e) { table.setAutoStartEditOnKeyStroke(!table.isAutoStartEditOnKeyStroke()); } }; addAction(frame, toggleAutoStart); frame.setVisible(true); } /** * Expose sorted column. * Example how to guarantee one column sorted at all times. */ public void interactiveAlwaysSorted() { final JXTable table = new JXTable(sortableTableModel) { @Override public void columnRemoved(TableColumnModelEvent e) { super.columnRemoved(e); if (!hasVisibleSortedColumn()) { toggleSortOrder(0); } } private boolean hasVisibleSortedColumn() { TableColumn column = getSortedColumn(); if (column instanceof TableColumnExt) { return ((TableColumnExt) column).isVisible(); } // JW: this path is not tested, don't really expect // non-ext column types, though JXTable must // cope with them return column != null; } }; table.setColumnControlVisible(true); JXFrame frame = wrapWithScrollingInFrame(table, "Always sorted"); frame.setVisible(true); } /** * Issue #282-swingx: compare disabled appearance of * collection views. * * Issue #1374-swingx: rollover effects on disabled collection views * */ public void interactiveDisabledCollectionViews() { final JXTable table = new JXTable(new AncientSwingTeam()); AbstractHyperlinkAction<Object> hyperlink = new AbstractHyperlinkAction<Object>() { @Override public void actionPerformed(ActionEvent e) { LOG.info("pressed link"); } }; table.getColumnExt(0).setCellRenderer(new DefaultTableRenderer(new HyperlinkProvider(hyperlink))); table.getColumnExt(0).setEditable(false); table.addHighlighter(new ColorHighlighter(HighlightPredicate.ROLLOVER_ROW, Color.MAGENTA, null)); table.setEnabled(false); final JXList list = new JXList(new String[] {"one", "two", "and something longer"}); list.setEnabled(false); list.setToolTipText("myText ... showing when disnabled?"); final JXTree tree = new JXTree(new FileSystemModel()); tree.setEnabled(false); JComponent box = Box.createHorizontalBox(); box.add(new JScrollPane(table)); box.add(new JScrollPane(list)); box.add(new JScrollPane(tree)); JXFrame frame = wrapInFrame(box, "disabled collection views"); AbstractAction action = new AbstractAction("toggle disabled") { @Override public void actionPerformed(ActionEvent e) { table.setEnabled(!table.isEnabled()); list.setEnabled(!list.isEnabled()); tree.setEnabled(!tree.isEnabled()); } }; addAction(frame, action); JLabel label = new JLabel("disable label"); label.setEnabled(false); label.setToolTipText("tooltip of disabled label"); addStatusComponent(frame, label); show(frame); } /** * Issue #281-swingx: header should be auto-repainted on changes to * header title, value. * * */ public void interactiveUpdateHeader() { final JXTable table = new JXTable(10, 2); JXFrame frame = wrapWithScrollingInFrame(table, "update header"); Action action = new AbstractAction("update headervalue") { int count; @Override public void actionPerformed(ActionEvent e) { table.getColumn(0).setHeaderValue("A" + count++); } }; addAction(frame, action); action = new AbstractAction("update column title") { int count; @Override public void actionPerformed(ActionEvent e) { table.getColumnExt(0).setTitle("A" + count++); } }; addAction(frame, action); frame.setVisible(true); } /** * Issue #281-swingx, Issue #334-swing: * header should be auto-repainted on changes to * header title, value. Must update size if appropriate. * * still open: core #4292511 - autowrap not really working * */ public void interactiveUpdateHeaderAndSizeRequirements() { final String[] alternate = { "simple", // "<html><b>This is a test of a large label to see if it wraps </font></b>", // "simple", "<html><center>Line 1<br>Line 2</center></html>" }; final JXTable table = new JXTable(10, 2); JXFrame frame = wrapWithScrollingInFrame(table, "update header"); Action action = new AbstractAction("update headervalue") { boolean first; @Override public void actionPerformed(ActionEvent e) { table.getColumn(1).setHeaderValue(first ? alternate[0] : alternate[1]); first = !first; } }; addAction(frame, action); frame.setVisible(true); } /** * Issue #??-swingx: column auto-sizing support. * */ public void interactiveTestExpandsToViewportWidth() { final JXTable table = new JXTable(); ColumnFactory factory = new ColumnFactory() { @Override public void configureTableColumn(TableModel model, TableColumnExt columnExt) { super.configureTableColumn(model, columnExt); if (model.getColumnClass(columnExt.getModelIndex()) == Integer.class) { // to see the effect: excess width is distributed relative // to the difference between maxSize and prefSize columnExt.setMaxWidth(200); } else { columnExt.setMaxWidth(1024); } } }; table.setColumnFactory(factory); table.setColumnControlVisible(true); table.setModel(sortableTableModel); table.setHorizontalScrollEnabled(true); table.packAll(); JXFrame frame = wrapWithScrollingInFrame(table, "expand to width"); Action toggleModel = new AbstractAction("toggle model") { @Override public void actionPerformed(ActionEvent e) { table.setModel(table.getModel() == sortableTableModel ? new DefaultTableModel(20, 4) : sortableTableModel); } }; addAction(frame, toggleModel); frame.setSize(table.getPreferredSize().width - 50, 300); frame.setVisible(true); LOG.info("table: " + table.getWidth()); LOG.info("Viewport: " + table.getParent().getWidth()); } /** * Issue ??: Anchor lost after receiving a structure changed. * Lead/anchor no longer automatically initialized - no visual clue * if table is focused. * */ public void interactiveTestToggleTableModelU6() { final DefaultTableModel tableModel = createAscendingModel(0, 20); final JTable table = new JTable(tableModel); // JW: need to explicitly set _both_ anchor and lead to >= 0 // need to set anchor first table.getSelectionModel().setAnchorSelectionIndex(0); table.getSelectionModel().setLeadSelectionIndex(0); table.getColumnModel().getSelectionModel().setAnchorSelectionIndex(0); table.getColumnModel().getSelectionModel().setLeadSelectionIndex(0); Action toggleAction = new AbstractAction("Toggle TableModel") { @Override public void actionPerformed(ActionEvent e) { TableModel model = table.getModel(); table.setModel(model.equals(tableModel) ? sortableTableModel : tableModel); } }; JXFrame frame = wrapWithScrollingInFrame(table, "JTable - anchor lost after structure changed"); addAction(frame, toggleAction); frame.setVisible(true); frame.pack(); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { // sanity - focus is on table LOG.info("isFocused? " + table.hasFocus()); LOG.info("who has focus? " + KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner()); } }); } /** * Issue #186-swingxProblem with lead/selection and buttons as editors: * - move focus (using arrow keys) to first editable boolean * - press space to toggle boolean * - move focus to next row (same column) * - press space to toggle boolean * - move back to first row (same column) * - press space: boolean is toggled and (that's the problem) * lead selection is moved to next row. * No problem in JTable. * */ public void interactiveTestCompareTableBoolean() { JXTable xtable = new JXTable(createModelWithBooleans()); JTable table = new JTable(createModelWithBooleans()); JXFrame frame = wrapWithScrollingInFrame(xtable, table, "Compare boolean renderer JXTable <--> JTable"); frame.setVisible(true); } private TableModel createModelWithBooleans() { String[] columnNames = { "text only", "Bool editable", "Bool not-editable" }; DefaultTableModel model = new DefaultTableModel(columnNames, 0) { @Override public Class<?> getColumnClass(int column) { return getValueAt(0, column).getClass(); } @Override public boolean isCellEditable(int row, int column) { return !getColumnName(column).contains("not"); } }; for (int i = 0; i < 4; i++) { model.addRow(new Object[] {"text only " + i, Boolean.TRUE, Boolean.TRUE }); } return model; } /** * Issue #89-swingx: ColumnControl not updated with ComponentOrientation. * */ public void interactiveRToLTableWithColumnControl() { final JXTable table = new JXTable(createAscendingModel(0, 20)); JScrollPane pane = new JScrollPane(table); final JXFrame frame = wrapInFrame(pane, "RToLScrollPane"); addComponentOrientationToggle(frame); Action toggleColumnControl = new AbstractAction("toggle column control") { @Override public void actionPerformed(ActionEvent e) { table.setColumnControlVisible(!table.isColumnControlVisible()); } }; addAction(frame, toggleColumnControl); frame.setVisible(true); } /** * Issue #179: Sorter does not use collator if cell content is * a String. * */ public void interactiveTestLocaleSorter() { Object[][] rowData = new Object[][] { new Object[] { Boolean.TRUE, "aa" }, new Object[] { Boolean.FALSE, "AB" }, new Object[] { Boolean.FALSE, "AC" }, new Object[] { Boolean.TRUE, "BA" }, new Object[] { Boolean.FALSE, "BB" }, new Object[] { Boolean.TRUE, "BC" } }; String[] columnNames = new String[] { "Critical", "Task" }; DefaultTableModel model = new DefaultTableModel(rowData, columnNames); // { // public Class getColumnClass(int column) { // return column == 1 ? String.class : super.getColumnClass(column); // } // }; final JXTable table = new JXTable(model); table.toggleSortOrder(1); JFrame frame = wrapWithScrollingInFrame(table, "locale sorting"); frame.setVisible(true); } /** * Issue #155-swingx: vertical scrollbar policy lost. * */ public void interactiveTestColumnControlConserveVerticalScrollBarPolicyAlways() { final JXTable table = new JXTable(); Action toggleAction = new AbstractAction("Toggle Control") { @Override public void actionPerformed(ActionEvent e) { table.setColumnControlVisible(!table.isColumnControlVisible()); } }; table.setModel(new DefaultTableModel(10, 5)); // initial state of column control visibility doesn't seem to matter // table.setColumnControlVisible(true); final JScrollPane scrollPane1 = new JScrollPane(table); scrollPane1.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); final JXFrame frame = wrapInFrame(scrollPane1, "JXTable Vertical ScrollBar Policy - always"); addAction(frame, toggleAction); Action packAction = new AbstractAction("Pack frame") { @Override public void actionPerformed(ActionEvent e) { frame.remove(scrollPane1); frame.add(scrollPane1); } }; addAction(frame, packAction); frame.setVisible(true); } /** * Issue #155-swingx: vertical scrollbar policy lost. * */ public void interactiveTestColumnControlConserveVerticalScrollBarPolicyNever() { final JXTable table = new JXTable(); Action toggleAction = new AbstractAction("Toggle Control") { @Override public void actionPerformed(ActionEvent e) { table.setColumnControlVisible(!table.isColumnControlVisible()); } }; table.setModel(new DefaultTableModel(10, 5)); // initial state of column control visibility doesn't seem to matter // table.setColumnControlVisible(true); final JScrollPane scrollPane1 = new JScrollPane(table); scrollPane1.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER); final JXFrame frame = wrapInFrame(scrollPane1, "JXTable Vertical ScrollBar Policy - never"); addAction(frame, toggleAction); Action packAction = new AbstractAction("Pack frame") { @Override public void actionPerformed(ActionEvent e) { frame.remove(scrollPane1); frame.add(scrollPane1); } }; addAction(frame, packAction); frame.setVisible(true); } /** * Issue #11: Column control not showing with few rows. * */ public void interactiveTestColumnControlFewRows() { final JXTable table = new JXTable(); Action toggleAction = new AbstractAction("Toggle Control") { @Override public void actionPerformed(ActionEvent e) { table.setColumnControlVisible(!table.isColumnControlVisible()); } }; table.setModel(new DefaultTableModel(10, 5)); table.setColumnControlVisible(true); JXFrame frame = wrapWithScrollingInFrame(table, "JXTable ColumnControl with few rows"); addAction(frame, toggleAction); frame.setVisible(true); } /** * check behaviour outside scrollPane * */ public void interactiveTestColumnControlWithoutScrollPane() { final JXTable table = new JXTable(); Action toggleAction = new AbstractAction("Toggle Control") { @Override public void actionPerformed(ActionEvent e) { table.setColumnControlVisible(!table.isColumnControlVisible()); } }; toggleAction.putValue(Action.SHORT_DESCRIPTION, "does nothing visible - no scrollpane"); table.setModel(new DefaultTableModel(10, 5)); table.setColumnControlVisible(true); JXFrame frame = wrapInFrame(table, "JXTable: Toggle ColumnControl outside ScrollPane"); addAction(frame, toggleAction); frame.setVisible(true); } /** * check behaviour of moving into/out of scrollpane. * */ public void interactiveTestToggleScrollPaneWithColumnControlOn() { final JXTable table = new JXTable(); table.setModel(new DefaultTableModel(10, 5)); table.setColumnControlVisible(true); final JXFrame frame = wrapInFrame(table, "JXTable: Toggle ScrollPane with Columncontrol on"); Action toggleAction = new AbstractAction("Toggle ScrollPane") { @Override public void actionPerformed(ActionEvent e) { Container parent = table.getParent(); boolean inScrollPane = parent instanceof JViewport; if (inScrollPane) { JScrollPane scrollPane = (JScrollPane) table.getParent().getParent(); frame.getContentPane().remove(scrollPane); frame.getContentPane().add(table); } else { parent.remove(table); parent.add(new JScrollPane(table)); } frame.pack(); } }; addAction(frame, toggleAction); frame.setVisible(true); } /** * TableColumnExt: user friendly resizable * */ public void interactiveTestColumnResizable() { final JXTable table = new JXTable(sortableTableModel); table.setColumnControlVisible(true); final TableColumnExt priorityColumn = table.getColumnExt("First Name"); JXFrame frame = wrapWithScrollingInFrame(table, "JXTable: Column with Min=Max not resizable"); Action action = new AbstractAction("Toggle MinMax of FirstName") { @Override public void actionPerformed(ActionEvent e) { // user-friendly resizable flag if (priorityColumn.getMinWidth() == priorityColumn.getMaxWidth()) { priorityColumn.setMinWidth(50); priorityColumn.setMaxWidth(150); } else { priorityColumn.setMinWidth(100); priorityColumn.setMaxWidth(100); } } }; addAction(frame, action); frame.setVisible(true); } /** */ public void interactiveTestToggleSortable() { final JXTable table = new JXTable(sortableTableModel); table.setColumnControlVisible(true); Action toggleSortableAction = new AbstractAction("Toggle Sortable") { @Override public void actionPerformed(ActionEvent e) { table.setSortable(!table.isSortable()); } }; JXFrame frame = wrapWithScrollingInFrame(table, "ToggleSortingEnabled Test"); addAction(frame, toggleSortableAction); frame.setVisible(true); } public void interactiveTestTableSizing1() { JXTable table = new JXTable(); table.setAutoCreateColumnsFromModel(false); table.setModel(tableModel); installLinkRenderer(table); table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); TableColumnExt columns[] = new TableColumnExt[tableModel.getColumnCount()]; for (int i = 0; i < columns.length; i++) { columns[i] = new TableColumnExt(i); table.addColumn(columns[i]); } columns[0].setPrototypeValue(new Integer(0)); columns[1].setPrototypeValue("Simple String Value"); columns[2].setPrototypeValue(new Integer(1000)); columns[3].setPrototypeValue(Boolean.TRUE); columns[4].setPrototypeValue(new Date(100)); columns[5].setPrototypeValue(new Float(1.5)); columns[6].setPrototypeValue(new LinkModel("Sun Micro", "_blank", tableModel.linkURL)); columns[7].setPrototypeValue(new Integer(3023)); columns[8].setPrototypeValue("John Doh"); columns[9].setPrototypeValue("23434 Testcase St"); columns[10].setPrototypeValue(new Integer(33333)); columns[11].setPrototypeValue(Boolean.FALSE); table.setVisibleRowCount(12); JFrame frame = wrapWithScrollingInFrame(table, "TableSizing1 Test"); frame.setVisible(true); } private void installLinkRenderer(JXTable table) { LinkModelAction<?> action = new LinkModelAction<LinkModel>() { @Override public void actionPerformed(ActionEvent e) { LOG.info("activated link: " + getTarget()); } }; TableCellRenderer linkRenderer = new DefaultTableRenderer( new HyperlinkProvider(action, LinkModel.class)); table.setDefaultRenderer(LinkModel.class, linkRenderer); } public void interactiveTestEmptyTableSizing() { JXTable table = new JXTable(0, 5); table.setColumnControlVisible(true); JFrame frame = wrapWithScrollingInFrame(table, "Empty Table (0 rows)"); frame.setVisible(true); } public void interactiveTestTableSizing2() { JXTable table = new JXTable(); table.setAutoCreateColumnsFromModel(false); table.setModel(tableModel); installLinkRenderer(table); TableColumnExt columns[] = new TableColumnExt[6]; int viewIndex = 0; for (int i = columns.length - 1; i >= 0; i--) { columns[viewIndex] = new TableColumnExt(i); table.addColumn(columns[viewIndex++]); } columns[5].setHeaderValue("String Value"); columns[5].setPrototypeValue("9999"); columns[4].setHeaderValue("String Value"); columns[4].setPrototypeValue("Simple String Value"); columns[3].setHeaderValue("Int Value"); columns[3].setPrototypeValue(new Integer(1000)); columns[2].setHeaderValue("Bool"); columns[2].setPrototypeValue(Boolean.FALSE); //columns[2].setSortable(false); columns[1].setHeaderValue("Date"); columns[1].setPrototypeValue(new Date(0)); //columns[1].setSortable(false); columns[0].setHeaderValue("Float"); columns[0].setPrototypeValue(new Float(5.5)); table.setRowHeight(24); table.setRowMargin(2); JFrame frame = wrapWithScrollingInFrame(table, "TableSizing2 Test"); frame.setVisible(true); } public void interactiveTestFocusedCellBackground() { TableModel model = new AncientSwingTeam() { @Override public boolean isCellEditable(int row, int column) { return column != 0; } }; JXTable xtable = new JXTable(model); xtable.setBackground(HighlighterFactory.NOTEPAD); JTable table = new JTable(model); table.setBackground(new Color(0xF5, 0xFF, 0xF5)); // ledger JFrame frame = wrapWithScrollingInFrame(xtable, table, "Unselected focused background: JXTable/JTable"); frame.setVisible(true); } public void interactiveTestTableViewProperties() { JXTable table = new JXTable(tableModel); installLinkRenderer(table); table.setIntercellSpacing(new Dimension(15, 15)); table.setRowHeight(48); JFrame frame = wrapWithScrollingInFrame(table, "TableViewProperties Test"); frame.setVisible(true); } public void interactiveColumnHighlighting() { final JXTable table = new JXTable(new AncientSwingTeam()); table.getColumnExt("Favorite Color").setHighlighters(new AbstractHighlighter() { @Override protected Component doHighlight(Component renderer, ComponentAdapter adapter) { Color color = (Color) adapter.getValue(); if (renderer instanceof JComponent) { ((JComponent) renderer).setBorder(BorderFactory.createLineBorder(color)); } return renderer; } }); JFrame frame = wrapWithScrollingInFrame(table, "Column Highlighter Test"); JToolBar bar = new JToolBar(); bar.add(new AbstractAction("Toggle") { boolean state = false; @Override public void actionPerformed(ActionEvent e) { if (state) { table.getColumnExt("No.").setHighlighters(new Highlighter[0]); state = false; } else { table.getColumnExt("No.").addHighlighter( new AbstractHighlighter(new HighlightPredicate() { @Override public boolean isHighlighted(Component renderer, ComponentAdapter adapter) { return adapter.getValue().toString().contains("8"); } }) { @Override protected Component doHighlight(Component renderer, ComponentAdapter adapter) { Font f = renderer.getFont().deriveFont(Font.ITALIC); renderer.setFont(f); return renderer; } } ); state = true; } } }); frame.add(bar, BorderLayout.NORTH); frame.setVisible(true); } @Override protected void setUp() throws Exception { super.setUp(); // super has LF specific tests... setSystemLF(true); // setLookAndFeel("Nimbus"); } /** * dummy */ @Test public void testDummy() { } }