/*
* $Id$
*
* Copyright 2004 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.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.InvocationTargetException;
import java.util.Vector;
import java.util.logging.Logger;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.event.TableModelEvent;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreePath;
import org.jdesktop.swingx.JXTreeTable.TreeTableModelAdapter;
import org.jdesktop.swingx.JXTreeTableUnitTest.JXTreeTableA;
import org.jdesktop.swingx.action.AbstractActionExt;
import org.jdesktop.swingx.decorator.Highlighter;
import org.jdesktop.swingx.decorator.HighlighterFactory;
import org.jdesktop.swingx.hyperlink.AbstractHyperlinkAction;
import org.jdesktop.swingx.renderer.CellContext;
import org.jdesktop.swingx.renderer.CheckBoxProvider;
import org.jdesktop.swingx.renderer.ComponentProvider;
import org.jdesktop.swingx.renderer.DefaultTableRenderer;
import org.jdesktop.swingx.renderer.DefaultTreeRenderer;
import org.jdesktop.swingx.renderer.HyperlinkProvider;
import org.jdesktop.swingx.renderer.LabelProvider;
import org.jdesktop.swingx.renderer.RendererVisualCheck.TextAreaProvider;
import org.jdesktop.swingx.renderer.StringValue;
import org.jdesktop.swingx.renderer.StringValues;
import org.jdesktop.swingx.renderer.WrappingIconPanel;
import org.jdesktop.swingx.renderer.WrappingProvider;
import org.jdesktop.swingx.table.ColumnFactory;
import org.jdesktop.swingx.table.TableColumnExt;
import org.jdesktop.swingx.test.ActionMapTreeTableModel;
import org.jdesktop.swingx.test.ComponentTreeTableModel;
import org.jdesktop.swingx.test.TreeTableUtils;
import org.jdesktop.swingx.treetable.AbstractMutableTreeTableNode;
import org.jdesktop.swingx.treetable.DefaultMutableTreeTableNode;
import org.jdesktop.swingx.treetable.DefaultTreeTableModel;
import org.jdesktop.swingx.treetable.FileSystemModel;
import org.jdesktop.swingx.treetable.MutableTreeTableNode;
import org.jdesktop.swingx.treetable.TreeTableModel;
import org.jdesktop.swingx.treetable.TreeTableNode;
import org.jdesktop.test.TableModelReport;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Test to exposed known issues of <code>JXTreeTable</code>. <p>
*
* Ideally, there would be at least one failing test method per open
* issue in the issue tracker. Plus additional failing test methods for
* not fully specified or not yet decided upon features/behaviour.<p>
*
* Once the issues are fixed and the corresponding methods are passing, they
* should be moved over to the XXTest.
*
* @author Jeanette Winzenburg
*/
public class JXTreeTableIssues extends InteractiveTestCase {
private static final Logger LOG = Logger.getLogger(JXTreeTableIssues.class
.getName());
public static void main(String[] args) {
setSystemLF(true);
JXTreeTableIssues test = new JXTreeTableIssues();
try {
// test.runInteractiveTests();
// test.runInteractiveTests(".*Combo.*");
// test.runInteractiveTests(".*Text.*");
// test.runInteractiveTests(".*TreeExpand.*");
// test.runInteractiveTests("interactive.*EditWith.*");
// test.runInteractiveTests("interactive.*Clip.*");
test.runInteractive("Prototype");
// test.runInteractiveTests("interactive.*CustomColor.*");
} catch (Exception e) {
System.err.println("exception when executing interactive tests:");
e.printStackTrace();
}
}
/**
* Issue #1509: configure column width based on prototype not working at all
* for hierarchical column.
*
*/
public void interactivePrototype() {
ColumnFactory factory = new ColumnFactory() {
@Override
protected int calcPrototypeWidth(JXTable table,
TableColumnExt columnExt) {
if (isHierarchicalPrototype(table, columnExt)) {
return calcHierarchicalPrototypeWidth((JXTreeTable) table, columnExt);
}
return super.calcPrototypeWidth(table, columnExt);
}
protected boolean isHierarchicalPrototype(JXTable table,
TableColumnExt columnExt) {
return (table instanceof JXTreeTable)
&& ((JXTreeTable) table).getTreeTableModel().getHierarchicalColumn() == columnExt.getModelIndex()
&& columnExt.getPrototypeValue() != null;
}
TreeCellRenderer dummy = new DefaultTreeCellRenderer();
protected int calcHierarchicalPrototypeWidth(JXTreeTable table,
TableColumnExt columnExt) {
JXTree renderer = (JXTree) getCellRenderer(table, columnExt);
// commented lines would be the obvious step down into the "real" sizing
// requirements, but giving reasonable result due to internal black magic
// TreeCellRenderer treeRenderer = renderer.getCellRenderer();
// Component comp = treeRenderer.getTreeCellRendererComponent(renderer,
// columnExt.getPrototypeValue(), false, false, false, -1, false);
// instead, measure the dummy
Component comp = dummy.getTreeCellRendererComponent(renderer,
columnExt.getPrototypeValue(), false, false, false, -1, false);
return Math.max(renderer.getPreferredSize().width, comp.getPreferredSize().width);
}
};
JXTreeTable table = new JXTreeTable();
table.setColumnFactory(factory);
table.setTreeTableModel(new FileSystemModel());
table.getColumnExt(0).setPrototypeValue("long longer longest still not enough to really see some effect of the prototype if availabel");
// Issue #1510: prototype value handling broken in underlying JXTable
// need to manually force the config
table.getColumnFactory().configureColumnWidths(table, table.getColumnExt(0));
showWithScrollingInFrame(table, "Issue #1509: prototype on hierarchical");
}
/**
* Issue #1379-swingx: support access to underlying TreeTableModel in TreeTableModelAdapter.
*
* PENDING JW:
* The bind isn't really fool-proof tight: its possible for subclasses to bind an
* arbitrary tree to a TreeTableModelAdapter.
* Dont want to change, though, as client code might have mis-used ...
*/
@Test(expected = IllegalStateException.class)
public void testTreeTableAdapterBind() {
JXTreeTableA table = new JXTreeTableA(createActionTreeModel());
TreeTableModelAdapter model = table.createAdapter(new JTree());
model.bind(table);
}
/**
* Custom renderer colors of Swingx DefaultTreeRenderer not respected.
* (same in J/X/Tree).
*
* A bit surprising - probably due to the half-hearted support (backward
* compatibility) of per-provider colors: they are set by the glueing
* renderer to the provider's default visuals. Which is useless if the
* provider is a wrapping provider - the wrappee's default visuals are unchanged.
*
* PENDING JW: think about complete removal. Client code should move over
* completely to highlighter/renderer separation anyway.
*
*
*/
public void interactiveXRendererCustomColor() {
JXTreeTable treeTable = new JXTreeTable(new FileSystemModel());
treeTable.addHighlighter(HighlighterFactory.createSimpleStriping());
DefaultTreeRenderer swingx = new DefaultTreeRenderer();
// in a treetable this has no effect: treetable.applyRenderer
// internally resets them to the same colors as tree itself
// (configured by the table's highlighters
swingx.setBackground(Color.YELLOW);
treeTable.setTreeCellRenderer(swingx);
JTree tree = new JXTree(treeTable.getTreeTableModel());
DefaultTreeRenderer other = new DefaultTreeRenderer();
other.setBackground(Color.YELLOW);
// other.setBackgroundSelectionColor(Color.RED);
tree.setCellRenderer(other);
JXFrame frame = wrapWithScrollingInFrame(treeTable, tree, "swingx renderers - highlight complete cell");
frame.setVisible(true);
}
/**
* Custom renderer colors of core DefaultTreeCellRenderer not respected.
* This is intentional: treeTable's highlighters must rule, so the
* renderer colors are used to force the treecellrenderer to use the
* correct values.
*/
public void interactiveCoreRendererCustomColor() {
JXTreeTable treeTable = new JXTreeTable(new FileSystemModel());
treeTable.addHighlighter(HighlighterFactory.createSimpleStriping());
DefaultTreeCellRenderer legacy = createBackgroundTreeRenderer();
// in a treetable this has no effect: treetable.applyRenderer
// internally resets them to the same colors as tree itself
// (configured by the table's highlighters
legacy.setBackgroundNonSelectionColor(Color.YELLOW);
legacy.setBackgroundSelectionColor(Color.RED);
treeTable.setTreeCellRenderer(legacy);
JTree tree = new JXTree(treeTable.getTreeTableModel());
DefaultTreeCellRenderer other = createBackgroundTreeRenderer();
other.setBackgroundNonSelectionColor(Color.YELLOW);
other.setBackgroundSelectionColor(Color.RED);
tree.setCellRenderer(other);
JXFrame frame = wrapWithScrollingInFrame(treeTable, tree, "legacy renderers - highlight complete cell");
frame.setVisible(true);
}
private DefaultTreeCellRenderer createBackgroundTreeRenderer() {
DefaultTreeCellRenderer legacy = new DefaultTreeCellRenderer() {
@Override
public Component getTreeCellRendererComponent(JTree tree,
Object value, boolean sel, boolean expanded, boolean leaf,
int row, boolean hasFocus) {
Component comp = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf,
row, hasFocus);
if (sel) {
comp.setBackground(getBackgroundSelectionColor());
} else {
comp.setBackground(getBackgroundNonSelectionColor());
}
return comp;
}
};
return legacy;
}
/**
* Issue #493-swingx: incorrect table events fired.
* Issue #592-swingx: (no structureChanged table events) is a special
* case of the former.
*
* Here: add support to prevent a structureChanged even when setting
* the root. May be required if the columns are stable and the
* model lazily loaded. Quick hack would be to add a clientProperty?
*
* @throws InvocationTargetException
* @throws InterruptedException
*/
public void testTableEventOnSetRootNoStructureChange() throws InterruptedException, InvocationTargetException {
TreeTableModel model = createCustomTreeTableModelFromDefault();
final JXTreeTable table = new JXTreeTable(model);
table.setRootVisible(true);
table.expandAll();
final TableModelReport report = new TableModelReport();
table.getModel().addTableModelListener(report);
((DefaultTreeTableModel) model).setRoot(new DefaultMutableTreeTableNode("other"));
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
assertEquals("tableModel must have fired", 1, report.getEventCount());
assertTrue("event type must be dataChanged " + TableModelReport.printEvent(report.getLastEvent()),
report.isDataChanged(report.getLastEvent()));
}
});
}
/**
* Issue #576-swingx: sluggish scrolling (?).
* Here - use default model
*/
public void interactiveScrollAlternateHighlightDefaultModel() {
final JXTable table = new JXTable(0, 6);
DefaultMutableTreeTableNode root = new DefaultMutableTreeTableNode("root");
for (int i = 0; i < 5000; i++) {
root.insert(new DefaultMutableTreeTableNode(i), i);
}
final JXTreeTable treeTable = new JXTreeTable(new DefaultTreeTableModel(root));
treeTable.expandAll();
table.setModel(treeTable.getModel());
final Highlighter hl =
HighlighterFactory.createAlternateStriping(UIManager.getColor("Panel.background"),
Color.WHITE);
treeTable.setHighlighters(hl);
table.setHighlighters(hl);
final JXFrame frame = wrapWithScrollingInFrame(treeTable, table, "sluggish scrolling");
Action toggleHighlighter = new AbstractActionExt("toggle highlighter") {
@Override
public void actionPerformed(ActionEvent e) {
if (treeTable.getHighlighters().length == 0) {
treeTable.addHighlighter(hl);
table.addHighlighter(hl);
} else {
treeTable.removeHighlighter(hl);
table.removeHighlighter(hl);
}
}
};
Action scroll = new AbstractActionExt("start scroll") {
@Override
public void actionPerformed(ActionEvent e) {
for (int i = 0; i < table.getRowCount(); i++) {
table.scrollRowToVisible(i);
treeTable.scrollRowToVisible(i);
}
}
};
addAction(frame, toggleHighlighter);
addAction(frame, scroll);
frame.setVisible(true);
}
/**
* Issue #576-swingx: sluggish scrolling (?)
*
* Here: use FileSystemModel
*/
public void interactiveScrollAlternateHighlight() {
final JXTable table = new JXTable(0, 6);
final JXTreeTable treeTable = new JXTreeTable(new FileSystemModel());
final Highlighter hl =
HighlighterFactory.createAlternateStriping(UIManager.getColor("Panel.background"),
Color.WHITE);
treeTable.setHighlighters(hl);
table.setHighlighters(hl);
final JXFrame frame = wrapWithScrollingInFrame(treeTable, table, "sluggish scrolling");
Action expand = new AbstractActionExt("start expand") {
@Override
public void actionPerformed(ActionEvent e) {
for (int i = 0; i < 5000; i++) {
treeTable.expandRow(i);
}
table.setModel(treeTable.getModel());
}
};
Action toggleHighlighter = new AbstractActionExt("toggle highlighter") {
@Override
public void actionPerformed(ActionEvent e) {
if (treeTable.getHighlighters().length == 0) {
treeTable.addHighlighter(hl);
table.addHighlighter(hl);
} else {
treeTable.removeHighlighter(hl);
table.removeHighlighter(hl);
}
}
};
Action scroll = new AbstractActionExt("start scroll") {
@Override
public void actionPerformed(ActionEvent e) {
for (int i = 0; i < table.getRowCount(); i++) {
table.scrollRowToVisible(i);
treeTable.scrollRowToVisible(i);
}
}
};
addAction(frame, expand);
addAction(frame, toggleHighlighter);
addAction(frame, scroll);
frame.setVisible(true);
}
/**
* Issue #493-swingx: JXTreeTable.TreeTableModelAdapter: Inconsistency
* firing update.
*
* Test update events after updating table.
*
* from tiberiu@dev.java.net
*
* @throws InvocationTargetException
* @throws InterruptedException
*/
public void testTableEventUpdateOnTreeTableSetValueForRoot() throws InterruptedException, InvocationTargetException {
TreeTableModel model = createCustomTreeTableModelFromDefault();
final JXTreeTable table = new JXTreeTable(model);
table.setRootVisible(true);
table.expandAll();
final int row = 0;
// sanity
assertEquals("JTree", table.getValueAt(row, 0).toString());
assertTrue("root must be editable", table.getModel().isCellEditable(0, 0));
final TableModelReport report = new TableModelReport();
table.getModel().addTableModelListener(report);
// doesn't fire or isn't detectable?
// Problem was: model was not-editable.
table.setValueAt("games", row, 0);
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
assertEquals("tableModel must have fired", 1, report.getEventCount());
assertEquals("the event type must be update " + TableModelReport.printEvent(report.getLastEvent())
, 1, report.getUpdateEventCount());
TableModelEvent event = report.getLastUpdateEvent();
assertEquals("the updated row ", row, event.getFirstRow());
}
});
}
// -------------- interactive tests
/**
* Issue #493-swingx: JXTreeTable.TreeTableModelAdapter: Inconsistency
* firing update on a recursive delete on a parent node.
*
* By recursive delete on a parent node it is understood that first we
* remove its children and then the parent node. After each child removed
* we are making an update over the parent. During this update the problem
* occurs: the index row for the parent is -1 and hence it is made an update
* over the row -1 (the header) and as it can be seen the preffered widths
* of column header are not respected anymore and are restored to the default
* preferences (all equal).
*
* from tiberiu@dev.java.net
*/
public void interactiveTreeTableModelAdapterDeleteUpdate() {
final DefaultTreeTableModel customTreeTableModel = (DefaultTreeTableModel)
createCustomTreeTableModelFromDefault();
final JXTreeTable table = new JXTreeTable(customTreeTableModel);
table.setRootVisible(true);
table.expandAll();
table.getColumn("A").setPreferredWidth(100);
table.getColumn("A").setMinWidth(100);
table.getColumn("A").setMaxWidth(100);
JXTree xtree = new JXTree(customTreeTableModel);
xtree.setRootVisible(true);
xtree.expandAll();
final JXFrame frame = wrapWithScrollingInFrame(table, xtree,
"JXTreeTable.TreeTableModelAdapter: Inconsistency firing update on recursive delete");
final MutableTreeTableNode deletedNode = (MutableTreeTableNode) table.getPathForRow(6).getLastPathComponent();
MutableTreeTableNode child1 = (MutableTreeTableNode) table.getPathForRow(6+1).getLastPathComponent();
MutableTreeTableNode child2 = (MutableTreeTableNode) table.getPathForRow(6+2).getLastPathComponent();
MutableTreeTableNode child3 = (MutableTreeTableNode) table.getPathForRow(6+3).getLastPathComponent();
MutableTreeTableNode child4 = (MutableTreeTableNode) table.getPathForRow(6+4).getLastPathComponent();
final MutableTreeTableNode[] children = {child1, child2, child3, child4 };
final String[] values = {"v1", "v2", "v3", "v4"};
final ActionListener l = new ActionListener() {
int count = 0;
@Override
public void actionPerformed(ActionEvent e) {
if (count > values.length) return;
if (count == values.length) {
customTreeTableModel.removeNodeFromParent(deletedNode);
count++;
} else {
// one in each run
removeChild(customTreeTableModel, deletedNode, children, values);
count++;
// all in one
// for (int i = 0; i < values.length; i++) {
// removeChild(customTreeTableModel, deletedNode, children, values);
// count++;
// }
}
}
/**
* @param customTreeTableModel
* @param deletedNode
* @param children
* @param values
*/
private void removeChild(final DefaultTreeTableModel customTreeTableModel, final MutableTreeTableNode deletedNode, final MutableTreeTableNode[] children, final String[] values) {
customTreeTableModel.removeNodeFromParent(children[count]);
customTreeTableModel.setValueAt(values[count], deletedNode, 0);
}
};
Action changeValue = new AbstractAction("delete node sports recursively") {
Timer timer;
@Override
public void actionPerformed(ActionEvent e) {
if (timer == null) {
timer = new Timer(10, l);
timer.start();
} else {
timer.stop();
setEnabled(false);
}
}
};
addAction(frame, changeValue);
frame.setVisible(true);
}
/**
* Issue #493-swingx: JXTreeTable.TreeTableModelAdapter: Inconsistency
* firing update. Use the second child of root - first is accidentally okay.
*
* from tiberiu@dev.java.net
*
* TODO DefaultMutableTreeTableNodes do not allow value changes, so this
* test will never work
*/
public void interactiveTreeTableModelAdapterUpdate() {
TreeTableModel customTreeTableModel = createCustomTreeTableModelFromDefault();
final JXTreeTable table = new JXTreeTable(customTreeTableModel);
table.setRootVisible(true);
table.expandAll();
table.setLargeModel(true);
JXTree xtree = new JXTree(customTreeTableModel);
xtree.setRootVisible(true);
xtree.expandAll();
final JXFrame frame = wrapWithScrollingInFrame(table, xtree,
"JXTreeTable.TreeTableModelAdapter: Inconsistency firing update");
Action changeValue = new AbstractAction("change sports to games") {
@Override
public void actionPerformed(ActionEvent e) {
String newValue = "games";
table.getTreeTableModel().setValueAt(newValue,
table.getPathForRow(6).getLastPathComponent(), 0);
}
};
addAction(frame, changeValue);
Action changeRoot = new AbstractAction("change root") {
@Override
public void actionPerformed(ActionEvent e) {
DefaultMutableTreeTableNode newRoot = new DefaultMutableTreeTableNode("new Root");
((DefaultTreeTableModel) table.getTreeTableModel()).setRoot(newRoot);
}
};
addAction(frame, changeRoot);
frame.pack();
frame.setVisible(true);
}
/**
* Issue #493-swingx: JXTreeTable.TreeTableModelAdapter: Inconsistency
* firing delete.
*
* from tiberiu@dev.java.net
*/
public void interactiveTreeTableModelAdapterDelete() {
final TreeTableModel customTreeTableModel = createCustomTreeTableModelFromDefault();
final JXTreeTable table = new JXTreeTable(customTreeTableModel);
table.setRootVisible(true);
table.expandAll();
JXTree xtree = new JXTree(customTreeTableModel);
xtree.setRootVisible(true);
xtree.expandAll();
final JXFrame frame = wrapWithScrollingInFrame(table, xtree,
"JXTreeTable.TreeTableModelAdapter: Inconsistency firing update");
Action changeValue = new AbstractAction("delete first child of sports") {
@Override
public void actionPerformed(ActionEvent e) {
MutableTreeTableNode firstChild = (MutableTreeTableNode) table.getPathForRow(6 +1).getLastPathComponent();
((DefaultTreeTableModel) customTreeTableModel).removeNodeFromParent(firstChild);
}
};
addAction(frame, changeValue);
frame.setVisible(true);
}
/**
* Issue #493-swingx: JXTreeTable.TreeTableModelAdapter: Inconsistency
* firing delete.
*
* from tiberiu@dev.java.net
*/
public void interactiveTreeTableModelAdapterMutateSelected() {
final TreeTableModel customTreeTableModel = createCustomTreeTableModelFromDefault();
final JXTreeTable table = new JXTreeTable(customTreeTableModel);
table.setRootVisible(true);
table.expandAll();
JXTree xtree = new JXTree(customTreeTableModel);
xtree.setRootVisible(true);
xtree.expandAll();
final JXFrame frame = wrapWithScrollingInFrame(table, xtree,
"JXTreeTable.TreeTableModelAdapter: Inconsistency firing delete expanded folder");
Action changeValue = new AbstractAction("delete selected node") {
@Override
public void actionPerformed(ActionEvent e) {
int row = table.getSelectedRow();
if (row < 0) return;
MutableTreeTableNode firstChild = (MutableTreeTableNode) table.getPathForRow(row).getLastPathComponent();
((DefaultTreeTableModel) customTreeTableModel).removeNodeFromParent(firstChild);
}
};
addAction(frame, changeValue);
Action changeValue1 = new AbstractAction("insert as first child of selected node") {
@Override
public void actionPerformed(ActionEvent e) {
int row = table.getSelectedRow();
if (row < 0) return;
MutableTreeTableNode firstChild = (MutableTreeTableNode) table.getPathForRow(row).getLastPathComponent();
MutableTreeTableNode newChild = new DefaultMutableTreeTableNode("inserted");
((DefaultTreeTableModel) customTreeTableModel)
.insertNodeInto(newChild, firstChild, 0);
}
};
addAction(frame, changeValue1);
frame.pack();
frame.setVisible(true);
}
/**
* Issue #493-swingx: JXTreeTable.TreeTableModelAdapter: Inconsistency
* firing delete.
*
* from tiberiu@dev.java.net
*/
public void interactiveTreeTableModelAdapterMutateSelectedDiscontinous() {
final TreeTableModel customTreeTableModel = createCustomTreeTableModelFromDefault();
final JXTreeTable table = new JXTreeTable(customTreeTableModel);
table.setRootVisible(true);
table.expandAll();
JXTree xtree = new JXTree(customTreeTableModel);
xtree.setRootVisible(true);
xtree.expandAll();
final JXFrame frame = wrapWithScrollingInFrame(table, xtree,
"JXTreeTable.TreeTableModelAdapter: Inconsistency firing delete expanded folder");
Action changeValue = new AbstractAction("delete selected node + sibling") {
@Override
public void actionPerformed(ActionEvent e) {
int row = table.getSelectedRow();
if (row < 0) return;
MutableTreeTableNode firstChild = (MutableTreeTableNode) table.getPathForRow(row).getLastPathComponent();
MutableTreeTableNode parent = (MutableTreeTableNode) firstChild.getParent();
MutableTreeTableNode secondNextSibling = null;
int firstIndex = parent.getIndex(firstChild);
if (firstIndex + 2 < parent.getChildCount()) {
secondNextSibling = (MutableTreeTableNode) parent.getChildAt(firstIndex + 2);
}
if (secondNextSibling != null) {
((DefaultTreeTableModel) customTreeTableModel).removeNodeFromParent(secondNextSibling);
}
((DefaultTreeTableModel) customTreeTableModel).removeNodeFromParent(firstChild);
}
};
addAction(frame, changeValue);
Action changeValue1 = new AbstractAction("insert as first child of selected node") {
@Override
public void actionPerformed(ActionEvent e) {
int row = table.getSelectedRow();
if (row < 0) return;
MutableTreeTableNode firstChild = (MutableTreeTableNode) table.getPathForRow(row).getLastPathComponent();
MutableTreeTableNode newChild = new DefaultMutableTreeTableNode("inserted");
((DefaultTreeTableModel) customTreeTableModel)
.insertNodeInto(newChild, firstChild, 0);
}
};
addAction(frame, changeValue1);
frame.pack();
frame.setVisible(true);
}
/**
* Creates and returns a custom model from JXTree default model. The model
* is of type DefaultTreeModel, allowing for easy insert/remove.
*
* @return
*/
private TreeTableModel createCustomTreeTableModelFromDefault() {
JXTree tree = new JXTree();
DefaultTreeModel treeModel = (DefaultTreeModel) tree.getModel();
TreeTableModel customTreeTableModel = TreeTableUtils
.convertDefaultTreeModel(treeModel);
return customTreeTableModel;
}
/**
* A TreeTableModel inheriting from DefaultTreeModel (to ease
* insert/delete).
*/
public static class CustomTreeTableModel extends DefaultTreeTableModel {
/**
* @param root
*/
public CustomTreeTableModel(TreeTableNode root) {
super(root);
}
@Override
public int getColumnCount() {
return 1;
}
@Override
public String getColumnName(int column) {
return "User Object";
}
@Override
public Object getValueAt(Object node, int column) {
return ((DefaultMutableTreeNode) node).getUserObject();
}
@Override
public boolean isCellEditable(Object node, int column) {
return true;
}
@Override
public void setValueAt(Object value, Object node, int column) {
((MutableTreeTableNode) node).setUserObject(value);
modelSupport.firePathChanged(new TreePath(getPathToRoot((TreeTableNode) node)));
}
}
/**
* Issue #??-swingx: hyperlink in JXTreeTable hierarchical column not
* active.
*
*/
public void interactiveTreeTableLinkRendererSimpleText() {
AbstractHyperlinkAction<Object> simpleAction = new AbstractHyperlinkAction<Object>(null) {
@Override
public void actionPerformed(ActionEvent e) {
LOG.info("hit: " + getTarget());
}
};
JXTreeTable tree = new JXTreeTable(new FileSystemModel());
HyperlinkProvider provider = new HyperlinkProvider(simpleAction);
tree.getColumn(2).setCellRenderer(new DefaultTableRenderer(provider));
tree.setTreeCellRenderer(new DefaultTreeRenderer( //provider));
new WrappingProvider(provider)));
// tree.setCellRenderer(new LinkRenderer(simpleAction));
tree.setHighlighters(HighlighterFactory.createSimpleStriping());
JFrame frame = wrapWithScrollingInFrame(tree, "table and simple links");
frame.setVisible(true);
}
/**
* Issue ??-swingx: hyperlink/rollover in hierarchical column.
*
*/
public void testTreeRendererInitialRollover() {
JXTreeTable tree = new JXTreeTable(new FileSystemModel());
assertEquals(tree.isRolloverEnabled(), ((JXTree) tree.getCellRenderer(0, 0)).isRolloverEnabled());
}
/**
* Issue ??-swingx: hyperlink/rollover in hierarchical column.
*
*/
public void testTreeRendererModifiedRollover() {
JXTreeTable tree = new JXTreeTable(new FileSystemModel());
tree.setRolloverEnabled(!tree.isRolloverEnabled());
assertEquals(tree.isRolloverEnabled(), ((JXTree) tree.getCellRenderer(0, 0)).isRolloverEnabled());
}
/**
* example how to use a custom component as
* renderer in tree column of TreeTable.
*
*/
public void interactiveTreeTableCustomRenderer() {
JXTreeTable tree = new JXTreeTable(new FileSystemModel());
StringValue sv = new StringValue( ){
@Override
public String getString(Object value) {
return "..." + StringValues.TO_STRING.getString(value);
}
};
ComponentProvider<?> provider = new CheckBoxProvider(sv);
// /**
// * custom tooltip: show row. Note: the context is that
// * of the rendering tree. No way to get at table state?
// */
// @Override
// protected void configureState(CellContext context) {
// super.configureState(context);
// rendererComponent.setToolTipText("Row: " + context.getRow());
// }
//
// };
tree.setTreeCellRenderer(new DefaultTreeRenderer(provider));
tree.setHighlighters(HighlighterFactory.createSimpleStriping());
JFrame frame = wrapWithScrollingInFrame(tree, "treetable and custom renderer");
frame.setVisible(true);
}
/**
* Quick example to use a TextArea in the hierarchical column
* of a treeTable. Not really working .. the wrap is not reliable?.
*
*/
public void interactiveTextAreaTreeTable() {
TreeTableModel model = createTreeTableModelWithLongNode();
JXTreeTable treeTable = new JXTreeTable(model);
treeTable.setVisibleRowCount(5);
treeTable.setRowHeight(50);
treeTable.getColumnExt(0).setPreferredWidth(200);
TreeCellRenderer renderer = new DefaultTreeRenderer(
new WrappingProvider(new TextAreaProvider()));
treeTable.setTreeCellRenderer(renderer);
showWithScrollingInFrame(treeTable, "TreeTable with text wrapping");
}
/**
* @return
*/
private TreeTableModel createTreeTableModelWithLongNode() {
MutableTreeTableNode root = createLongNode("some really, maybe really really long text - "
+ "wrappit .... where needed ");
root.insert(createLongNode("another really, maybe really really long text - "
+ "with nothing but junk. wrappit .... where needed"), 0);
root.insert(createLongNode("another really, maybe really really long text - "
+ "with nothing but junk. wrappit .... where needed"), 0);
MutableTreeTableNode node = createLongNode("some really, maybe really really long text - "
+ "wrappit .... where needed ");
node.insert(createLongNode("another really, maybe really really long text - "
+ "with nothing but junk. wrappit .... where needed"), 0);
root.insert(node, 0);
root.insert(createLongNode("another really, maybe really really long text - "
+ "with nothing but junk. wrappit .... where needed"), 0);
Vector<String> ids = new Vector<String>();
ids.add("long text");
ids.add("dummy");
return new DefaultTreeTableModel(root, ids);
}
/**
* @param string
* @return
*/
private MutableTreeTableNode createLongNode(final String string) {
AbstractMutableTreeTableNode node = new AbstractMutableTreeTableNode() {
Object rnd = Math.random();
@Override
public int getColumnCount() {
return 2;
}
@Override
public Object getValueAt(int column) {
if (column == 0) {
return string;
}
return rnd;
}
};
node.setUserObject(string);
return node;
}
/**
* Experiments to try and understand clipping issues: occasionally, the text
* in the tree column is clipped even if there is enough space available.
*
* To visualize the rendering component's size we use a WrappingIconProvider
* which sets a red border.
*
* Calling packxx might pose a problem: for non-large models the node size
* is cached. In this case, packing before replacing the renderer will lead
* to incorrect sizes which are hard to invalidate (no way except faking a
* structural tree event? temporaryly set large model and back, plus repaint
* works). <p>
*
* both pack-moments seem okay (without ellipses, always filling the box),
* probably due to fixing swingx-1232 (by forcing the xtree to fire a
* change notification on setRenderer)
*/
public void interactiveTreeTableClipIssueWrappingProvider() {
final JXTreeTable treeTable = new JXTreeTable(createActionTreeModel());
treeTable.setHorizontalScrollEnabled(true);
treeTable.setColumnControlVisible(true);
// BEWARE: do not pack before setting the renderer
treeTable.packColumn(0, -1);
StringValue format = new StringValue() {
@Override
public String getString(Object value) {
if (value instanceof Action) {
return ((Action) value).getValue(Action.NAME) + "xx";
}
return StringValues.TO_STRING.getString(value);
}
};
ComponentProvider<?> tableProvider = new LabelProvider(format);
WrappingProvider wrappingProvider = new WrappingProvider(tableProvider) {
Border redBorder = BorderFactory.createLineBorder(Color.RED);
@Override
public WrappingIconPanel getRendererComponent(CellContext context) {
super.getRendererComponent(context);
rendererComponent.setBorder(redBorder);
return rendererComponent;
}
};
DefaultTreeRenderer treeCellRenderer = new DefaultTreeRenderer(
wrappingProvider);
treeTable.setTreeCellRenderer(treeCellRenderer);
treeTable.setHighlighters(HighlighterFactory.createSimpleStriping());
// at this point a pack is okay, caching will get the correct values
// treeTable.packColumn(0, -1);
final JXTree tree = new JXTree(treeTable.getTreeTableModel());
tree.setCellRenderer(treeCellRenderer);
tree.setScrollsOnExpand(false);
JXFrame frame = wrapWithScrollingInFrame(treeTable, tree,
"treetable and tree with wrapping provider");
// revalidate doesn't help
Action revalidate = new AbstractActionExt("revalidate") {
@Override
public void actionPerformed(ActionEvent e) {
treeTable.revalidate();
tree.revalidate();
treeTable.repaint();
}
};
// hack around incorrect cached node sizes
Action large = new AbstractActionExt("large-circle") {
@Override
public void actionPerformed(ActionEvent e) {
treeTable.setLargeModel(true);
treeTable.setLargeModel(false);
treeTable.repaint();
}
};
addAction(frame, revalidate);
addAction(frame, large);
show(frame);
}
/**
* Experiments to try and understand clipping issues: occasionally, the text
* in the tree column is clipped even if there is enough space available.
*
* Here we don't change any renderers.
*/
public void interactiveTreeTableClipIssueDefaultRenderer() {
final JXTreeTable treeTable = new JXTreeTable(createActionTreeModel());
treeTable.setHorizontalScrollEnabled(true);
treeTable.setRootVisible(true);
treeTable.collapseAll();
treeTable.packColumn(0, -1);
final JTree tree = new JTree(treeTable.getTreeTableModel());
tree.collapseRow(0);
JXFrame frame = wrapWithScrollingInFrame(treeTable,
tree, "JXTreeTable vs. core JTree: default renderer");
Action revalidate = new AbstractActionExt("revalidate") {
@Override
public void actionPerformed(ActionEvent e) {
treeTable.revalidate();
tree.revalidate();
} };
addAction(frame, revalidate);
frame.setVisible(true);
}
/**
* Experiments to try and understand clipping issues: occasionally, the text
* in the tree column is clipped even if there is enough space available.
* Here we set a custom (unchanged default) treeCellRenderer which
* removes ellipses altogether.
*
*/
public void interactiveTreeTableClipIssueCustomDefaultRenderer() {
TreeCellRenderer renderer = new DefaultTreeCellRenderer() {
@Override
public Component getTreeCellRendererComponent(JTree tree,
Object value, boolean sel, boolean expanded, boolean leaf,
int row, boolean hasFocus) {
return super.getTreeCellRendererComponent(tree, value, sel,
expanded, leaf, row, hasFocus);
}
};
final JXTreeTable treeTable = new JXTreeTable(createActionTreeModel());
treeTable.setTreeCellRenderer(renderer);
treeTable.setHorizontalScrollEnabled(true);
treeTable.setRootVisible(true);
treeTable.collapseAll();
treeTable.packColumn(0, -1);
final JTree tree = new JTree(treeTable.getTreeTableModel());
tree.setCellRenderer(renderer);
tree.collapseRow(0);
JXFrame frame = wrapWithScrollingInFrame(treeTable, tree,
"JXTreeTable vs. JTree: custom default renderer without clip");
Action revalidate = new AbstractActionExt("revalidate") {
@Override
public void actionPerformed(ActionEvent e) {
treeTable.revalidate();
tree.revalidate();
}
};
addAction(frame, revalidate);
frame.setVisible(true);
}
/**
* Dirty example how to configure a custom renderer to use
* treeTableModel.getValueAt(...) for showing.
*
*/
public void interactiveTreeTableGetValueRenderer() {
JXTreeTable tree = new JXTreeTable(new ComponentTreeTableModel(new JXFrame()));
ComponentProvider<?> provider = new CheckBoxProvider(StringValues.TO_STRING) {
@Override
protected String getValueAsString(CellContext context) {
// this is dirty because the design idea was to keep the renderer
// unaware of the context type
TreeTableModel model = (TreeTableModel) ((JXTree) context.getComponent()).getModel();
// beware: currently works only if the node is not a DefaultMutableTreeNode
// otherwise the WrappingProvider tries to be smart and replaces the node
// by the userObject before passing on to the wrappee!
Object nodeValue = model.getValueAt(context.getValue(), 0);
return formatter.getString(nodeValue);
}
};
tree.setTreeCellRenderer(new DefaultTreeRenderer(provider));
tree.expandAll();
tree.setHighlighters(HighlighterFactory.createSimpleStriping());
JFrame frame = wrapWithScrollingInFrame(tree, "treeTable and getValueAt renderer");
frame.setVisible(true);
}
//------------- unit tests
/**
* Issue #399-swingx: editing terminated by selecting editing row.
*
*/
public void testSelectionKeepsEditingWithExpandsTrue() {
JXTreeTable treeTable = new JXTreeTable(new FileSystemModel()) {
@Override
public boolean isCellEditable(int row, int column) {
return true;
}
};
// sanity: default value of expandsSelectedPath
assertTrue(treeTable.getExpandsSelectedPaths());
boolean canEdit = treeTable.editCellAt(1, 2);
// sanity: editing started
assertTrue(canEdit);
// sanity: nothing selected
assertTrue(treeTable.getSelectionModel().isSelectionEmpty());
int editingRow = treeTable.getEditingRow();
treeTable.setRowSelectionInterval(editingRow, editingRow);
assertEquals("after selection treeTable editing state must be unchanged", canEdit, treeTable.isEditing());
}
/**
* Issue #212-jdnc: reuse editor, install only once.
*
*/
public void testReuseEditor() {
//TODO rework this test, since we no longer use TreeTableModel.class
// JXTreeTable treeTable = new JXTreeTable(treeTableModel);
// CellEditor editor = treeTable.getDefaultEditor(TreeTableModel.class);
// assertTrue(editor instanceof TreeTableCellEditor);
// treeTable.setTreeTableModel(simpleTreeTableModel);
// assertSame("hierarchical editor must be unchanged", editor,
// treeTable.getDefaultEditor(TreeTableModel.class));
fail("#212-jdnc - must be revisited after treeTableModel overhaul");
}
/**
* sanity: toggling select/unselect via mouse the lead is
* always painted, doing unselect via model (clear/remove path)
* seems to clear the lead?
*
*/
public void testBasicTreeLeadSelection() {
JXTree tree = new JXTree();
TreePath path = tree.getPathForRow(0);
tree.setSelectionPath(path);
assertEquals(0, tree.getSelectionModel().getLeadSelectionRow());
assertEquals(path, tree.getLeadSelectionPath());
tree.removeSelectionPath(path);
assertNotNull(tree.getLeadSelectionPath());
assertEquals(0, tree.getSelectionModel().getLeadSelectionRow());
}
/**
* Issue #341-swingx: missing synch of lead.
* test lead after setting selection via table.
*
* PENDING: this passes locally, fails on server
*/
public void testLeadSelectionFromTable() {
JXTreeTable treeTable = prepareTreeTable(false);
assertEquals(-1, treeTable.getSelectionModel().getLeadSelectionIndex());
assertEquals(-1, treeTable.getTreeSelectionModel().getLeadSelectionRow());
treeTable.setRowSelectionInterval(0, 0);
assertEquals(treeTable.getSelectionModel().getLeadSelectionIndex(),
treeTable.getTreeSelectionModel().getLeadSelectionRow());
fail("lead selection synch passes locally, fails on server");
}
/**
* Issue #341-swingx: missing synch of lead.
* test lead after setting selection via treeSelection.
* PENDING: this passes locally, fails on server
*
*/
public void testLeadSelectionFromTree() {
JXTreeTable treeTable = prepareTreeTable(false);
assertEquals(-1, treeTable.getSelectionModel().getLeadSelectionIndex());
assertEquals(-1, treeTable.getTreeSelectionModel().getLeadSelectionRow());
treeTable.getTreeSelectionModel().setSelectionPath(treeTable.getPathForRow(0));
assertEquals(treeTable.getSelectionModel().getLeadSelectionIndex(),
treeTable.getTreeSelectionModel().getLeadSelectionRow());
assertEquals(0, treeTable.getTreeSelectionModel().getLeadSelectionRow());
fail("lead selection synch passes locally, fails on server");
}
/**
* Issue #341-swingx: missing synch of lead.
* test lead after remove selection via tree.
*
*/
public void testLeadAfterRemoveSelectionFromTree() {
JXTreeTable treeTable = prepareTreeTable(true);
treeTable.getTreeSelectionModel().removeSelectionPath(
treeTable.getTreeSelectionModel().getLeadSelectionPath());
assertEquals(treeTable.getSelectionModel().getLeadSelectionIndex(),
treeTable.getTreeSelectionModel().getLeadSelectionRow());
}
/**
* Issue #341-swingx: missing synch of lead.
* test lead after clear selection via table.
*
*/
public void testLeadAfterClearSelectionFromTable() {
JXTreeTable treeTable = prepareTreeTable(true);
treeTable.clearSelection();
assertEquals(treeTable.getSelectionModel().getLeadSelectionIndex(),
treeTable.getTreeSelectionModel().getLeadSelectionRow());
}
/**
* Issue #341-swingx: missing synch of lead.
* test lead after clear selection via table.
*
*/
public void testLeadAfterClearSelectionFromTree() {
JXTreeTable treeTable = prepareTreeTable(true);
treeTable.getTreeSelectionModel().clearSelection();
assertEquals(treeTable.getSelectionModel().getLeadSelectionIndex(),
treeTable.getTreeSelectionModel().getLeadSelectionRow());
}
/**
* creates and configures a treetable for usage in selection tests.
*
* @param selectFirstRow boolean to indicate if the first row should
* be selected.
* @return
*/
protected JXTreeTable prepareTreeTable(boolean selectFirstRow) {
JXTreeTable treeTable = new JXTreeTable(new ComponentTreeTableModel(new JXFrame()));
treeTable.setRootVisible(true);
// sanity: assert that we have at least two rows to change selection
assertTrue(treeTable.getRowCount() > 1);
if (selectFirstRow) {
treeTable.setRowSelectionInterval(0, 0);
}
return treeTable;
}
public void testDummy() {
}
/**
* @return
*/
private TreeTableModel createActionTreeModel() {
JXTable table = new JXTable(10, 10);
table.setHorizontalScrollEnabled(true);
return new ActionMapTreeTableModel(table);
}
}