📄 tree.java
字号:
package org.uispec4j;
import junit.framework.Assert;
import junit.framework.AssertionFailedError;
import org.uispec4j.assertion.Assertion;
import org.uispec4j.utils.ArrayUtils;
import org.uispec4j.utils.ColorUtils;
import org.uispec4j.utils.Utils;
import javax.accessibility.AccessibleComponent;
import javax.accessibility.AccessibleContext;
import javax.swing.*;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Wrapper for JTree components.<p>
* The nodes of a tree are identified using stringified paths. For instance, for the tree
* below:
* <pre><code>
* root
* |
* +- child
* | |
* | +- subChild
* |
* +- otherChild
* |
* +- otherSubChild
* </code></pre>
* the "subChild" element is identified with the following path:
* <pre><code>
* child/subChild
* </code></pre>
* Note that when defining paths the root element name is always omitted. The root
* node path itself is denoted by an empty string ("").<p>
* The default path separator is "/". However, this separator can be customized as follows:
* <ul>
* <li>By setting it on a given Tree instance using {@link Tree#setSeparator(String)}</li>
* <li>By setting it on all new Tree instances using {@link Tree#setDefaultSeparator(String)}</li>
* <li>By setting it on all new Tree instances using the <code>uispec4j.tree.separator</code>
* property.</li>
* </ul>
* When using paths, it is also possible to use substrings from the displayed node names.
* For instance, instead of writing:
* <pre><code>
* otherChild/otherSubChild
* </code></pre>
* one can write:
* <pre><code>
* other/sub
* </code></pre>
* <p/>
* The contents of the tree can be checked with {@link #contentEquals(String)},
* which is used as follows:
* <pre><code>
* assertTrue(jTree.contentEquals("root\n" +
* " child1\n" +
* " child1_1\n" +
* " child2"));
* </code></pre>
* <p/>
* The conversion between the values (Strings) given in the tests and the values
* actually displayed by the JTree renderer is performed by a dedicated
* {@link TreeCellValueConverter}, which retrieves the graphical component that draws
* the tree nodes and determines the displayed value accordingly.
* A {@link DefaultTreeCellValueConverter} is used by default by the Tree component.
*/
public class Tree extends AbstractUIComponent {
public static final String TYPE_NAME = "tree";
public static final Class[] SWING_CLASSES = {JTree.class};
static final String SEPARATOR_PROPERTY = "uispec4j.tree.separator";
private JTree jTree;
static String defaultSeparator = "/";
private String separator;
private TreeCellValueConverter cellValueConverter = new DefaultTreeCellValueConverter();
private static final Pattern COLOR_PROPERTY_PATTERN =
Pattern.compile(" #\\(.*color=([\\w]+)\\)");
public Tree(JTree jTree) {
this.jTree = jTree;
this.separator = initSeparator();
}
private static String initSeparator() {
String property = System.getProperty(Tree.SEPARATOR_PROPERTY);
if ((property != null) && (property.length() > 0)) {
return property;
}
return Tree.defaultSeparator;
}
public String getDescriptionTypeName() {
return TYPE_NAME;
}
public Component getAwtComponent() {
return jTree;
}
/**
* Returns the JTree wrapped by this component.
*/
public JTree getJTree() {
return jTree;
}
/**
* Sets the separator to be used for specifying node paths in this jTree instance.
*/
public void setSeparator(String separator) {
checkSeparator(separator);
this.separator = separator;
}
private static void checkSeparator(String separator) {
if (separator == null) {
throw new IllegalArgumentException("Separator must not be null");
}
else if (separator.length() == 0) {
throw new IllegalArgumentException("Separator must not be empty");
}
}
/**
* Returns the separator currently used for specifying node paths in this jTree instance.
*/
public String getSeparator() {
return separator;
}
/**
* Sets the separator to be used for specifying node paths in new jTree instances.
*/
public static void setDefaultSeparator(String separator) {
checkSeparator(separator);
defaultSeparator = separator;
}
/**
* Sets a new converter for retrieving the text displayed on the tree cells.
*/
public void setCellValueConverter(TreeCellValueConverter converter) {
this.cellValueConverter = converter;
}
/**
* Checks the nodes structure displayed by the jTree.<p>
* The expected contents is a newline (\n) separated string where nodes are
* indented with two-space steps.
* For instance:
* <code><pre>
* assertTrue(jTree.contentEquals("root\n" +
* " child1\n" +
* " child1_1\n" +
* " child2"));
* </pre></code>
* Text display properties such as boldness and color can be checked using a "#(...)"
* specifier.
* For instance:
* <code><pre>
* assertTrue(jTree.contentEquals("root\n" +
* " child1 #(bold)\n" +
* " child1_1 #(bold,color=red)\n" +
* " child2"));
* </pre></code>
* The properties are defined as follows:
* <ul>
* <li>The "bold" property must be present if and only if the node text is bold</li>
* <li>The "color" property value can be numeric ("0000ee") or approximative ("blue")
* (see the <a href="http://www.uispec4j.org/usingcolors.html">Using colors</a> page
* for more information)</li>
* <li>The "bold" property, if present, must be placed before the "color" property</li>
* </ul>
*/
public Assertion contentEquals(final String expectedContents) {
return new Assertion() {
public void check() {
String trimmedExpected = expectedContents.trim();
Assert.assertTrue("Expected tree description should not be empty",
(trimmedExpected != null) && (trimmedExpected.length() > 0));
checkContents(trimmedExpected);
}
};
}
/**
* Checks that a node identified by the given path is present in the jTree.
*/
public Assertion contains(final String path) {
return new Assertion() {
public void check() {
getTreePath(path);
}
};
}
/**
* Selects the root node of the jTree.
*/
public void selectRoot() {
jTree.setSelectionPath(new TreePath(jTree.getModel().getRoot()));
}
/**
* Expands the current jTree selection with a given node.
*/
public void addToSelection(String path) {
jTree.addSelectionPath(getTreePath(path));
}
/**
* Removes the given node from the current jTree selection.
*/
public void removeFromSelection(String path) {
TreePath jTreePath = getTreePath(path);
jTree.removeSelectionPath(jTreePath);
}
/**
* Expands the current jTree selection with a node identified by its position in its parent node.
* <p>This method is preferred over {@link #addToSelection(String)} when there are several nodes
* with the same name under a given parent.
*/
public void addToSelection(String parentPath, int childIndex) {
jTree.addSelectionPath(computeChildTreePath(parentPath, childIndex));
}
/**
* Removes the current selection.
*/
public void clearSelection() {
jTree.clearSelection();
}
/**
* Sets the selection on the given node.
*/
public void select(String path) {
jTree.clearSelection();
jTree.setSelectionPath(getTreePath(path));
}
/**
* Sets the jTree selection on a node identified by its position in its parent node.
* <p>This method is preferred over {@link #select(String)} when there are several nodes
* with the same name under a given parent.
*/
public void select(String parentPath, int childIndex) {
int childCount = getChildCount(parentPath);
if (childIndex < 0 || childCount <= childIndex) {
throw new RuntimeException("No child found under '" +
parentPath +
"' for index '" + childIndex + "'");
}
jTree.clearSelection();
jTree.addSelectionPath(computeChildTreePath(parentPath, childIndex));
}
/**
* Selects under a given parent all the nodes whose name contains a given substring.
* This method will throw an exception if no the parent path was invalid or if no children
* were found.
*/
public void select(String parentPath, String childSubstring) {
TreePath jTreePath = getTreePath(parentPath);
TreeModel model = jTree.getModel();
Object node = jTreePath.getLastPathComponent();
List subPaths = new ArrayList();
for (int i = 0, max = model.getChildCount(node); i < max; i++) {
Object child = model.getChild(node, i);
String text = getShownText(child);
if (text.indexOf(childSubstring) >= 0) {
subPaths.add(jTreePath.pathByAddingChild(child));
}
}
if (subPaths.isEmpty()) {
Assert.fail("No children found");
}
TreePath[] result = (TreePath[])subPaths.toArray(new TreePath[subPaths.size()]);
jTree.setSelectionPaths(result);
}
public void select(String[] paths) {
jTree.clearSelection();
for (int i = 0; i < paths.length; i++) {
String path = paths[i];
jTree.addSelectionPath(getTreePath(path));
}
}
/**
* Simulates a user left-click on a given node.
*/
public void click(String path) {
TreePath jTreePath = getTreePath(path);
jTree.setSelectionPath(jTreePath);
clickOnTreePath(getTreePath(path), false, Key.Modifier.NONE);
}
/**
* Simulates a user right-click on a given node.
*/
public void rightClick(String path) {
TreePath jTreePath = getTreePath(path);
jTree.setSelectionPath(jTreePath);
clickOnTreePath(jTreePath, true, Key.Modifier.NONE);
}
/**
* Right-clicks on the first selected node.
*/
public void rightClickInSelection() {
TreePath selectionPath = jTree.getSelectionPath();
Assert.assertNotNull("There is no current selection", selectionPath);
clickOnTreePath(selectionPath, true, Key.Modifier.NONE);
}
public Trigger triggerClick(final String path) {
return new Trigger() {
public void run() throws Exception {
click(path);
}
};
}
public Trigger triggerRightClick(final String path) {
return new Trigger() {
public void run() throws Exception {
rightClick(path);
}
};
}
public Trigger triggerRightClickInSelection() {
return new Trigger() {
public void run() throws Exception {
rightClickInSelection();
}
};
}
/**
* Returns the number of children of a given node.
*/
public int getChildCount(String path) {
TreePath jTreePath = getTreePath(path);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -