/* * $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.Cursor; import java.awt.Dimension; import java.awt.Point; import java.awt.event.ActionEvent; import java.io.File; import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JPopupMenu; import javax.swing.JTree; import javax.swing.SwingUtilities; import javax.swing.ToolTipManager; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.plaf.basic.BasicTreeUI; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeCellEditor; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeCellRenderer; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; 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.CompoundHighlighter; import org.jdesktop.swingx.decorator.HighlightPredicate; import org.jdesktop.swingx.decorator.Highlighter; import org.jdesktop.swingx.decorator.HighlighterFactory; import org.jdesktop.swingx.decorator.PatternPredicate; import org.jdesktop.swingx.decorator.SearchPredicate; import org.jdesktop.swingx.decorator.HighlightPredicate.DepthHighlightPredicate; import org.jdesktop.swingx.renderer.DefaultListRenderer; import org.jdesktop.swingx.renderer.DefaultTreeRenderer; import org.jdesktop.swingx.renderer.IconValues; import org.jdesktop.swingx.renderer.StringValue; import org.jdesktop.swingx.renderer.StringValues; import org.jdesktop.swingx.renderer.WrappingProvider; import org.jdesktop.swingx.renderer.HighlighterClientVisualCheck.FontHighlighter; import org.jdesktop.swingx.test.ActionMapTreeTableModel; import org.jdesktop.swingx.treetable.FileSystemModel; import org.jdesktop.swingx.treetable.TreeTableNode; import org.jdesktop.test.AncientSwingTeam; public class JXTreeVisualCheck extends JXTreeUnitTest { @SuppressWarnings("all") private static final Logger LOG = Logger.getLogger(JXTreeVisualCheck.class .getName()); public static void main(String[] args) { // setSystemLF(true); JXTreeVisualCheck test = new JXTreeVisualCheck(); setLAF("Wind"); try { // test.runInteractiveTests(); // test.runInteractiveTests("interactive.*Renderer.*"); // test.runInteractiveTests("interactive.*RToL.*"); // test.runInteractiveTests("interactive.*Revalidate.*"); // test.runInteractiveTests("interactiveRootExpansionTest"); // test.runInteractiveTests("interactive.*UpdateUI.*"); // test.runInteractiveTests("interactive.*CellHeight.*"); // test.runInteractiveTests("interactive.*RendererSize.*"); // test.runInteractive("NextMatch"); test.runInteractive("PopupTrigger"); } 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() { JXTree table = new JXTree(); table.expandAll(); JPopupMenu popup = new JPopupMenu(); Action action = new AbstractAction("cell found in actionPerformed") { @Override public void actionPerformed(ActionEvent e) { JXTree table = SwingXUtilities.getAncestor(JXTree.class, (Component) e.getSource()); Point trigger = table.getPopupTriggerLocation(); Point cell = null; if (trigger != null) { int row = table.getRowForLocation(trigger.x, trigger.y); table.setSelectionRow(row); cell = new Point(0, 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? // JXTree tree = SwingXUtilities.getAncestor(JXTree.class, // (Component) e.getSource()); JXTree tree = (JXTree) ((JPopupMenu) e.getSource()).getInvoker(); Point trigger = tree.getPopupTriggerLocation(); Point cell = null; if (trigger != null) { int row = tree.getRowForLocation(trigger.x, trigger.y); tree.setSelectionRow(row); tree.setLeadSelectionPath(tree.getPathForRow(row)); cell = new Point(0, 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 1483-swingx: getNextMatch must respect StringValue */ public void interactiveNextMatch() { JXTree tree = new JXTree(new FileSystemModel(new File("."))); tree.setCellRenderer(new DefaultTreeRenderer(IconValues.FILE_ICON, StringValues.FILE_NAME)); showWithScrollingInFrame(tree, "nextMatch?"); } public void interactiveDynamicCellHeightTree() { final JXTree tree = new JXTree(AncientSwingTeam.createNamedColorTreeModel()); TreeSelectionListener l = new TreeSelectionListener() { @Override public void valueChanged(TreeSelectionEvent e) { LOG.info("height " + tree.getRowHeight()); // tree.setRowHeight(-1); SwingUtilities.invokeLater(new Runnable() { public void run() { tree.invalidateCellSizeCache(); } }); } }; tree.addTreeSelectionListener(l); tree.setCellRenderer(new DefaultTreeRenderer()); // tree.setLargeModel(true); tree.setRowHeight(0); HighlightPredicate selected = new HighlightPredicate() { @Override public boolean isHighlighted(Component renderer, ComponentAdapter adapter) { return adapter.isSelected(); } }; Highlighter hl = new FontHighlighter(selected, tree.getFont().deriveFont(50f)); tree.addHighlighter(hl); showWithScrollingInFrame(tree, "big font on focus"); } /** * Issue #1231-swingx: tree cell renderer size problems. * * Cache not invalidated on setCellRenderer due to not firing a * propertyChange (it's wrapped). Problem or not is LAF dependent ;-) * The not-firing is clearly a bug. * */ public void interactiveRendererSize() { JXTree tree = new JXTree(); tree.setCellRenderer(new CustomCellRenderer()); tree.expandAll(); showWithScrollingInFrame(tree, "sizing problems in renderer"); } private static class CustomCellRenderer extends DefaultTreeCellRenderer { @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { return super.getTreeCellRendererComponent(tree, "XX " + value, selected, expanded, leaf, row, hasFocus); } } /** * Issue #862-swingx: JXTree - add api for selection colors. */ public void interactiveSelectionColors() { final JXTree tree = new JXTree(); // use SwingX renderer which is aware of per-tree selection colors tree.setCellRenderer(new DefaultTreeRenderer()); final Color uiBackground = tree.getSelectionBackground(); final Color uiForeground = tree.getSelectionForeground(); Action toggleSelectionColors = new AbstractAction("toggle selection colors") { public void actionPerformed(ActionEvent e) { if (tree.getSelectionBackground() == uiBackground) { tree.setSelectionBackground(Color.BLUE); tree.setSelectionForeground(Color.RED); } else { tree.setSelectionBackground(uiBackground); tree.setSelectionForeground(uiForeground); } } }; JXFrame frame = wrapWithScrollingInFrame(tree, "selection color property in JXTree"); addAction(frame, toggleSelectionColors); show(frame); } /** * Issue #862-swingx: JXTree - add api for selection colors. * Compare with JList repaint behaviour. */ public void interactiveSelectionColorsList() { final JXList tree = new JXList(new Object[]{"one", "two", "three"}); // use SwingX renderer which is aware of per-tree selection colors tree.setCellRenderer(new DefaultListRenderer()); final Color uiBackground = tree.getSelectionBackground(); final Color uiForeground = tree.getSelectionForeground(); Action toggleSelectionColors = new AbstractAction("toggle selection colors") { public void actionPerformed(ActionEvent e) { if (tree.getSelectionBackground() == uiBackground) { tree.setSelectionBackground(Color.BLUE); tree.setSelectionForeground(Color.RED); } else { tree.setSelectionBackground(uiBackground); tree.setSelectionForeground(uiForeground); } } }; JXFrame frame = wrapWithScrollingInFrame(tree, "selection color property - compare list repaint"); addAction(frame, toggleSelectionColors); show(frame); } /** * Requirements * - no icons, use IconValue.NONE * - don't unwrap user object */ public void interactiveNoIconsNoUnwrap() { TreeModel model = new ActionMapTreeTableModel(new JXTable()); JXTree tree = new JXTree(model); StringValue sv = new StringValue() { public String getString(Object value) { if ((value instanceof TreeTableNode) && ((TreeTableNode) value).getColumnCount() > 0) { value = ((TreeTableNode) value).getValueAt(0); } return StringValues.TO_STRING.getString(value); } }; DefaultTreeRenderer renderer = new DefaultTreeRenderer(IconValues.NONE, sv); ((WrappingProvider) renderer.getComponentProvider()).setUnwrapUserObject(false); tree.setCellRenderer(renderer); JXFrame frame = wrapWithScrollingInFrame(tree, "WrappingProvider: no icons, no unwrapped userObject"); frame.pack(); frame.setSize(400, 200); frame.setVisible(true); } /** * * Requirements: * - striping effect on leaf, extend to full width of tree * - striping relative to parent (not absolute position of row) * * Trick (from forum): set the rendering component's pref width * in the renderer. * * Applied to SwingX: * - use a highlighter for the pref width setting * - use a predicate to decide which striping to turn on * * Problem: difficult to get rid off size cache of BasicTreeUI. * * The sizing of the nodes is cached before the actual expansion. * That is the row index is invalid at the time of messaging the * renderer, so decoration which effects the size (like setting pref * f.i.) is ignored. Except for largeModel and fixed row height. * * Note: as is, it cannot cope with RToL component orientation. */ public void interactiveExpandToWidthHighlighter() { final JXTree tree = new JXTree(); tree.setCellRenderer(new DefaultTreeRenderer()); tree.expandRow(3); tree.setRowHeight(20); tree.setLargeModel(true); tree.setRootVisible(false); tree.setShowsRootHandles(true); int indent = ((BasicTreeUI) tree.getUI()).getLeftChildIndent() + ((BasicTreeUI) tree.getUI()).getRightChildIndent(); int depthOffset = getDepthOffset(tree); HighlightPredicate evenChild = new HighlightPredicate() { public boolean isHighlighted(Component renderer, ComponentAdapter adapter) { if (!(adapter.getComponent() instanceof JTree)) return false; TreePath path = ((JTree) adapter.getComponent()).getPathForRow(adapter.row); return path == null ? false : (adapter.row - ((JTree) adapter.getComponent()).getRowForPath(path.getParentPath())) % 2 == 0; } }; HighlightPredicate oddChild = new HighlightPredicate() { public boolean isHighlighted(Component renderer, ComponentAdapter adapter) { if (!(adapter.getComponent() instanceof JTree)) return false; TreePath path = ((JTree) adapter.getComponent()).getPathForRow(adapter.row); return path == null ? false : (adapter.row - ((JTree) adapter.getComponent()).getRowForPath(path.getParentPath())) % 2 == 1; } }; final ExtendToWidthHighlighter extendToWidthHighlighter = new ExtendToWidthHighlighter(null, indent, depthOffset); tree.setHighlighters( new CompoundHighlighter( HighlightPredicate.IS_LEAF, extendToWidthHighlighter, new ColorHighlighter(evenChild, HighlighterFactory.BEIGE, null), new ColorHighlighter(oddChild, HighlighterFactory.LINE_PRINTER, null)), new ColorHighlighter(HighlightPredicate.IS_FOLDER, null, Color.RED) ); final JXFrame frame = wrapWithScrollingInFrame(tree, "tree-wide cell renderer"); Action rootVisible = new AbstractActionExt("toggle root visible") { public void actionPerformed(ActionEvent e) { tree.setRootVisible(!tree.isRootVisible()); extendToWidthHighlighter.setDepthOffset(getDepthOffset(tree)); } }; addAction(frame, rootVisible); Action handleVisible = new AbstractActionExt("toggle handles") { public void actionPerformed(ActionEvent e) { tree.setShowsRootHandles(!tree.getShowsRootHandles()); extendToWidthHighlighter.setDepthOffset(getDepthOffset(tree)); } }; addAction(frame, handleVisible); addComponentOrientationToggle(frame); show(frame, 400, 400); } /** * C&p from BasicTreeUI: adjust the depth to root/handle visibility. * @param tree * @return */ protected int getDepthOffset(JTree tree) { if(tree.isRootVisible()) { if(tree.getShowsRootHandles()) { return 1; } } else if(!tree.getShowsRootHandles()) { return -1; } return 0; } /** * Highlighter which sets the preferredWidth of the renderer relative to the * target component's width. Very special requirement for tree rendering: * extend the coloring all the way from the node to the boundary of the tree. * An alternative would be to us a renderer which is layout in such * a way so by default. */ public static class ExtendToWidthHighlighter extends AbstractHighlighter { private int indent; private int depthOffset; public ExtendToWidthHighlighter(HighlightPredicate predicate, int indent, int depthOffset) { super(predicate); this.indent = indent; this.depthOffset = depthOffset; } public void setDepthOffset(int offset) { if (offset == this.depthOffset) return; this.depthOffset = offset; fireStateChanged(); } @Override protected Component doHighlight(Component component, ComponentAdapter adapter) { Dimension dim = component.getPreferredSize(); int width = adapter.getComponent().getWidth() - (adapter.getDepth() + depthOffset) * indent ; dim.width = Math.max(dim.width, width); component.setPreferredSize(dim); return component; } } public void interactiveExpandWithHighlighters() { JXTree tree = new JXTree(); Highlighter searchHighlighter = new ColorHighlighter(new SearchPredicate("\\Qe\\E"), null, Color.RED); tree.addHighlighter(searchHighlighter); showWithScrollingInFrame(tree, "NPE on tree expand with highlighter"); } /** * visually check if invokesStopCellEditing jumps in on focusLost. * */ public void interactiveToggleEditProperties() { final JXTree table = new JXTree(); table.setEditable(true); DefaultTreeCellEditor editor = new DefaultTreeCellEditor(null, null) { @Override public boolean stopCellEditing() { String value = String.valueOf(getCellEditorValue()); if (value.startsWith("s")) { return false; } return super.stopCellEditing(); } }; table.setCellEditor(editor); JXFrame frame = wrapWithScrollingInFrame(table, new JButton("something to focus"), "JXTree: toggle invokesStopEditing "); Action toggleTerminate = new AbstractAction("toggleInvokesStop") { public void actionPerformed(ActionEvent e) { table.setInvokesStopCellEditing(!table.getInvokesStopCellEditing()); } }; addAction(frame, toggleTerminate); frame.setVisible(true); } /** * visualize editing of the hierarchical column, both * in a tree and a xTree switching CO. * using DefaultXTreeCellEditor. */ public void interactiveXTreeEditingRToL() { JTree tree = new JTree(); tree.setEditable(true); JXTree xTree = new JXTree(); xTree.setCellRenderer(new DefaultTreeRenderer()); xTree.setEditable(true); final JXFrame frame = wrapWithScrollingInFrame(xTree, tree, "XEditing: compare JXTree vs. JTree"); addComponentOrientationToggle(frame); addMessage(frame, "JXTree configured with SwingX renderer/treeEditor"); show(frame); } /** * Issue ??: Background highlighters not working on JXTree. * */ public void interactiveUnselectedFocusedBackground() { JXTree xtree = new JXTree(treeTableModel); xtree.setCellRenderer(new DefaultTreeRenderer()); xtree.setBackground(new Color(0xF5, 0xFF, 0xF5)); JTree tree = new JTree(treeTableModel); tree.setBackground(new Color(0xF5, 0xFF, 0xF5)); JXFrame frame = wrapWithScrollingInFrame(xtree, tree, "Unselected focused background: JXTree/JTree" ); addMessage(frame, "xtree uses swingx renderer"); } /** * Issue #503-swingx: JXList rolloverEnabled disables custom cursor. * * Sanity test for JXTree (looks okay). * */ public void interactiveTestRolloverHighlightCustomCursor() { JXTree tree = new JXTree(treeTableModel); tree.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); tree.setRolloverEnabled(true); tree.setHighlighters(createRolloverHighlighter(true)); showWithScrollingInFrame(tree, "foreground rollover, custom cursor " ); } public void interactiveTestDepthHighlighter() { JXTree tree = new JXTree(treeTableModel); tree.setHighlighters(createDepthHighlighters()); showWithScrollingInFrame(tree, "Depth highlighter" ); } public void interactiveTestEditabilityHighlighter() { JXTree tree = new JXTree(treeTableModel); tree.setEditable(true); tree.setHighlighters(new ColorHighlighter(HighlightPredicate.EDITABLE, Color.WHITE, Color.RED)); showWithScrollingInFrame(tree, "Editability highlighter" ); } /** * Issue ??: Background highlighters not working on JXTree. * * Works with SwingX renderer */ public void interactiveTestRolloverHighlightBackground() { JXTree tree = new JXTree(treeTableModel); tree.setRolloverEnabled(true); tree.setCellRenderer(new DefaultTreeRenderer()); tree.setHighlighters(createRolloverHighlighter(false)); JXFrame frame = wrapWithScrollingInFrame(tree, "Rollover - background " ); addMessage(frame, "here we use a SwingX renderer - backgound okay"); show(frame); } private Highlighter createRolloverHighlighter(boolean useForeground) { Color color = new Color(0xF0, 0xF0, 0xE0); //LegacyHighlighter.ledgerBackground.getBackground(); Highlighter highlighter = new ColorHighlighter( HighlightPredicate.ROLLOVER_ROW, useForeground ? null : color, useForeground ? color.darker() : null); return highlighter; } private Highlighter[] createDepthHighlighters() { Highlighter[] highlighters = new Highlighter[2]; highlighters[0] = new ColorHighlighter(new DepthHighlightPredicate(1), Color.WHITE, Color.RED); highlighters[1] = new ColorHighlighter(new DepthHighlightPredicate(2), Color.WHITE, Color.BLUE); return highlighters; } /** * Issue ??: Background highlighters not working on JXTree. * * It's a problem of core DefaultTreeCellRenderer. SwingX renderers are okay. * */ public void interactiveTestHighlighters() { JXTree tree = new JXTree(treeTableModel); String pattern = "o"; tree.setHighlighters(new ColorHighlighter(new PatternPredicate(pattern, 0), Color.YELLOW, Color.RED), HighlighterFactory.createSimpleStriping(HighlighterFactory.LINE_PRINTER)); JXFrame frame = wrapWithScrollingInFrame(tree, "Foreground/background Highlighter: " + pattern); addMessage(frame, "here we use a core default renderer - background highlighter not working!"); show(frame); } public void interactiveTestToolTips() { JXTree tree = new JXTree(treeTableModel); // JW: don't use this idiom - Stackoverflow... // multiple delegation - need to solve or discourage tree.setCellRenderer(createRenderer()); // JW: JTree does not automatically register itself // should JXTree? ToolTipManager toolTipManager = ToolTipManager.sharedInstance(); toolTipManager.registerComponent(tree); showWithScrollingInFrame(tree, "tooltips"); } private TreeCellRenderer createRenderer() { final TreeCellRenderer delegate = new DefaultTreeCellRenderer(); TreeCellRenderer renderer = new TreeCellRenderer() { public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { Component result = delegate.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); ((JComponent) result).setToolTipText(String.valueOf(tree.getPathForRow(row))); return result; } }; return renderer; } /** * test if lineStyle client property is respected by JXTree. * Note that some LFs don't respect anyway (WinLF f.i.) */ public void interactiveTestLineStyle() { JXTree tree = new JXTree(treeTableModel); tree.setDragEnabled(true); tree.putClientProperty("JTree.lineStyle", "None"); showWithScrollingInFrame(tree, "LineStyle Test - none"); } /** * setting tree properties: JXTree is updated properly. */ public void interactiveTestTreeProperties() { final JXTree treeTable = new JXTree(treeTableModel); Action toggleHandles = new AbstractAction("Toggle Handles") { public void actionPerformed(ActionEvent e) { treeTable.setShowsRootHandles(!treeTable.getShowsRootHandles()); } }; Action toggleRoot = new AbstractAction("Toggle Root") { public void actionPerformed(ActionEvent e) { treeTable.setRootVisible(!treeTable.isRootVisible()); } }; treeTable.setRowHeight(22); JXFrame frame = wrapWithScrollingInFrame(treeTable, "Toggle Tree properties "); addAction(frame, toggleRoot); addAction(frame, toggleHandles); show(frame); } /** * Ensure that the root node is expanded if invisible and child added. */ public void interactiveRootExpansionTest() { final DefaultMutableTreeNode root = new DefaultMutableTreeNode("root"); final DefaultTreeModel model = new DefaultTreeModel(root); final JXTree tree = new JXTree(model); tree.setRootVisible(false); assertFalse(tree.isExpanded(new TreePath(root))); Action addChild = new AbstractAction("Add Root Child") { private int counter = 0; public void actionPerformed(ActionEvent e) { root.add(new DefaultMutableTreeNode("Child " + (counter + 1))); model.nodesWereInserted(root, new int[]{counter}); counter++; assertTrue(tree.isExpanded(new TreePath(root))); } }; JXFrame frame = wrapWithScrollingInFrame(tree, "Root Node Expansion Test"); addAction(frame, addChild); show(frame); } }