/*******************************************************************************
*
* Copyright (C) 2010 Jalian Systems Private Ltd.
* Copyright (C) 2010 Contributors to Marathon OSS Project
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Project website: http://www.marathontesting.com
* Help: Marathon help forum @ http://groups.google.com/group/marathon-testing
*
*******************************************************************************/
package net.sourceforge.marathon.component;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.Properties;
import javax.swing.JLabel;
import javax.swing.JTree;
import javax.swing.tree.TreeCellEditor;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import net.sourceforge.marathon.action.ClickAction;
import net.sourceforge.marathon.recorder.WindowMonitor;
import net.sourceforge.marathon.util.Retry;
public class MTreeNode extends MCellComponent {
private String path;
private int row = -1;
public MTreeNode(Component component, String name, Object pathOrPoint, ComponentFinder finder, WindowMonitor windowMonitor) {
super(component, name, finder, windowMonitor);
if (pathOrPoint instanceof String || pathOrPoint instanceof Properties) {
Properties props;
if (pathOrPoint instanceof String)
props = parseProperties((String) pathOrPoint, new String[][] { { "Path" } });
else
props = (Properties) pathOrPoint;
path = props.getProperty("Path");
if (path == null) {
MTreeNode node = (MTreeNode) getCollectionComponent().findMatchingComponent(props);
if (node == null)
throw new ComponentException("Could not find matching treenode for given property list: " + props,
finder.getScriptModel(), windowMonitor);
path = node.getPath();
}
row = eventQueueRunner.invokeInteger(getTreeComponent(), "getRowForPath", new Object[] { getTreePath() },
new Class[] { TreePath.class });
if (row < 0)
throw new ComponentException("Could not find row for treepath for Tree(" + getMComponentName() + ") path: " + path,
finder.getScriptModel(), windowMonitor);
} else {
Point point = (Point) pathOrPoint;
StringBuffer pathBuild = new StringBuffer("");
TreePath treePath = (TreePath) eventQueueRunner.invoke(getTreeComponent(), "getClosestPathForLocation", new Object[] {
Integer.valueOf((int) point.getX()), Integer.valueOf((int) point.getY()) }, new Class[] { Integer.TYPE,
Integer.TYPE });
if (treePath != null) {
row = eventQueueRunner.invokeInteger(getTreeComponent(), "getRowForPath", new Object[] { treePath },
new Class[] { TreePath.class });
boolean rootVisible = eventQueueRunner.invokeBoolean(getTreeComponent(), "isRootVisible");
int start = rootVisible ? 0 : 1;
Object[] objs = treePath.getPath();
for (int i = start; i < objs.length; i++) {
String pathString;
if (objs[i].toString() == null)
pathString = "";
else
pathString = escapeSpecialCharacters(getTextForNodeObject(objs[i]));
pathBuild.append("/" + pathString);
}
}
this.path = pathBuild.toString();
}
}
public String getPath() {
return path;
}
public MTreeNode(JTree tree, String name, int row, ComponentFinder finder, WindowMonitor windowMonitor) {
super(tree, name, finder, windowMonitor);
this.row = row;
TreePath treePath = (TreePath) eventQueueRunner.invoke(tree, "getPathForRow", new Object[] { Integer.valueOf(row) },
new Class[] { Integer.TYPE });
if (treePath == null)
throw new ComponentException("Could not find path for row for Tree(" + getMComponentName() + ") row: " + row,
finder.getScriptModel(), windowMonitor);
StringBuffer pathBuild = new StringBuffer("");
boolean rootVisible = eventQueueRunner.invokeBoolean(tree, "isRootVisible");
int start = rootVisible ? 0 : 1;
if (treePath != null) {
Object[] objs = treePath.getPath();
for (int i = start; i < objs.length; i++) {
String pathString;
if (objs[i].toString() == null)
pathString = "";
else
pathString = escapeSpecialCharacters(getTextForNodeObject(objs[i]));
pathBuild.append("/" + pathString);
}
}
this.path = pathBuild.toString();
}
private void assertTrue(String message, boolean condition) {
if (!condition) {
throw new RuntimeException(message);
}
}
public void click(int numberOfClicks, int modifiers, Point position) {
if (row == -1)
return;
if (position == null) {
Rectangle rect = (Rectangle) eventQueueRunner.invoke(getTreeComponent(), "getRowBounds",
new Object[] { Integer.valueOf(row) }, new Class[] { Integer.TYPE });
if (rect == null)
throw new RuntimeException("Leaf " + getComponentInfo() + "for tree " + getMComponentName() + " not visible...");
position = new Point((int) rect.getCenterX(), (int) rect.getCenterY());
}
super.click(numberOfClicks, modifiers, position);
}
public String getComponentInfo() {
return createPropertyMapString(new String[] { "Path" });
}
private TreePath getTreePath() {
try {
new Retry("Search for cell component", ComponentFinder.getRetryInterval(), ComponentFinder.getRetryCount(),
new Retry.Attempt() {
public void perform() {
if (findTreePath(new StringBuffer()) == null)
retry();
}
});
} catch (Exception e) {
return null;
}
StringBuffer searchedPath = new StringBuffer();
TreePath treePath = findTreePath(searchedPath);
assertTrue("no tree node corresponding to <" + searchedPath + "> in <" + this.path + ">", treePath != null);
return treePath;
}
private TreePath findTreePath(StringBuffer searchedPath) {
String[] tokens = path.substring(1).split("(?<!\\\\)/");
TreeModel treeModel = (TreeModel) eventQueueRunner.invoke(getTreeComponent(), "getModel");
if (treeModel == null)
throw new ComponentException("Unable to get columnModel for tree: '" + getMComponentName() + "'",
finder.getScriptModel(), windowMonitor);
Object rootNode = treeModel.getRoot();
assertTrue("invalid path specifier <" + path + ">", tokens.length >= 1);
boolean rootVisible = eventQueueRunner.invokeBoolean(getTreeComponent(), "isRootVisible");
int start = rootVisible ? 1 : 0;
TreePath treePath = new TreePath(rootNode);
if (rootVisible) {
String rootNodeText = unescapeSpecialCharacters(tokens[0]);
searchedPath.append("/" + rootNodeText);
assertTrue("JTree does not have a root node!", rootNode != null);
assertTrue(
"JTree root node does not match: Expected </" + getPathText(treePath) + "> Actual: <" + searchedPath.toString()
+ ">", searchedPath.toString().equals("/" + getPathText(treePath)));
}
for (int i = start; i < tokens.length; i++) {
String childText = unescapeSpecialCharacters(tokens[i]);
searchedPath.append("/" + childText);
boolean matched = false;
eventQueueRunner.invoke(getTreeComponent(), "expandPath", new Object[] { treePath }, new Class[] { TreePath.class });
swingWait();
for (int j = 0; j < treeModel.getChildCount(treePath.getLastPathComponent()); j++) {
Object child = treeModel.getChild(treePath.getLastPathComponent(), j);
TreePath childPath = treePath.pathByAddingChild(child);
if (childText.equals(getPathText(childPath))) {
treePath = childPath;
matched = true;
break;
}
}
if (!matched)
return null;
}
return treePath;
}
private JTree getTreeComponent() {
return (JTree) getComponent();
}
// get the object specified by path and return its corresponding text value
private String getPathText(TreePath path) {
Object lastPathComponent = path.getLastPathComponent();
if (lastPathComponent == null)
return "";
return getTextForNodeObject(lastPathComponent);
}
private String getTextForNodeObject(Object lastPathComponent) {
JTree tree = getTreeComponent();
TreeCellRenderer renderer = (TreeCellRenderer) eventQueueRunner.invoke(tree, "getCellRenderer");
if (renderer == null)
return null;
Component c = renderer.getTreeCellRendererComponent(tree, lastPathComponent, false, false, false, 0, false);
if (c != null && c instanceof JLabel) {
return ((JLabel) c).getText();
}
return lastPathComponent.toString();
}
public MCollectionComponent getCollectionComponent() {
return new MTree(getTreeComponent(), getMComponentName(), getFinder(), windowMonitor);
}
public String escapeSpecialCharacters(String name) {
return name.replaceAll("/", "\\\\/");
}
public String unescapeSpecialCharacters(String name) {
return name.replaceAll("\\\\/", "/");
}
public boolean keyNeeded(KeyEvent e) {
return super.keyNeeded(e, true);
}
public int clickNeeded(MouseEvent e) {
if (isPopupTrigger(e))
return ClickAction.RECORD_CLICK;
int toggleClickCount = eventQueueRunner.invokeInteger(getTreeComponent(), "getToggleClickCount");
if (toggleClickCount == 0)
return ClickAction.RECORD_CLICK;
return ClickAction.RECORD_NONE;
}
public void setCurrentSelection() {
TreePath path = getTreePath();
eventQueueRunner.invoke(getTreeComponent(), "setSelectionPath", new Object[] { path }, new Class[] { TreePath.class });
}
public String getText() {
if (row == -1 || finder == null)
return null;
boolean isEditing = eventQueueRunner.invokeBoolean(getTreeComponent(), "isEditing");
String text = null;
if (isEditing) {
MComponent editor = getEditor();
if (editor != null)
text = editor.getText();
} else {
MComponent renderer = getRenderer();
if (renderer != null)
text = renderer.getText();
}
if (text != null)
return prepareText(text);
return null;
}
private String prepareText(String text) {
text = stripHTMLTags(text);
return escapeSpecialCharacters(text);
}
public MComponent getRenderer() {
JTree tree = getTreeComponent();
TreeCellRenderer renderer = (TreeCellRenderer) eventQueueRunner.invoke(tree, "getCellRenderer");
if (renderer == null)
return null;
boolean isSelected = eventQueueRunner.invokeBoolean(tree, "isRowSelected", new Object[] { Integer.valueOf(row) },
new Class[] { Integer.TYPE });
boolean isExpanded = eventQueueRunner.invokeBoolean(tree, "isExpanded", new Object[] { Integer.valueOf(row) },
new Class[] { Integer.TYPE });
TreePath treePath = (TreePath) eventQueueRunner.invoke(tree, "getPathForRow", new Object[] { Integer.valueOf(row) },
new Class[] { Integer.TYPE });
if (treePath == null)
return null;
boolean isLeaf = false;
Component rendererComponent = renderer.getTreeCellRendererComponent(tree, treePath.getLastPathComponent(), isSelected,
isExpanded, isLeaf, row, true);
if (rendererComponent == null)
return null;
return finder.getMComponentByComponent(rendererComponent, "doesn't matter", null);
}
public MComponent getEditor() {
JTree tree = getTreeComponent();
TreeCellEditor cellEditor = (TreeCellEditor) eventQueueRunner.invoke(tree, "getCellEditor");
if (cellEditor == null)
return null;
boolean isSelected = eventQueueRunner.invokeBoolean(tree, "isRowSelected", new Object[] { Integer.valueOf(row) },
new Class[] { Integer.TYPE });
boolean isExpanded = eventQueueRunner.invokeBoolean(tree, "isExpanded", new Object[] { Integer.valueOf(row) },
new Class[] { Integer.TYPE });
TreePath treePath = (TreePath) eventQueueRunner.invoke(tree, "getPathForRow", new Object[] { Integer.valueOf(row) },
new Class[] { Integer.TYPE });
if (treePath == null)
return null;
boolean isLeaf = false;
Component editor = cellEditor.getTreeCellEditorComponent(tree, treePath.getLastPathComponent(), isSelected, isExpanded,
isLeaf, row);
if (editor == null)
return null;
if (editor instanceof Container) {
editor = ((Container) editor).getComponent(0);
}
return finder.getMComponentByComponent(editor, "doesn't matter", null);
}
public boolean isMComponentEditable() {
return true;
}
public void setText(String text) {
if (row == -1)
return;
getTreeComponent().startEditingAtPath(getTreePath());
MComponent editor = getEditor();
if (editor == null) {
System.err.println("Warning: Editor component not found for an table cell with select() call:\n" + "\tfor table: "
+ getTree().getMComponentName() + " at path: " + getComponentInfo());
return;
}
editor.setText(text, true);
eventQueueRunner.invoke(getTreeComponent(), "stopEditing");
swingWait();
}
protected Class<?>[] getPropertyAccessMethodParameters(String property) {
return new Class[] { Integer.TYPE };
}
protected Object[] getPropertyAccessMethodArguments(String property) {
return new Object[] { Integer.valueOf(row) };
}
public Point getLocation() {
Rectangle bounds = (Rectangle) eventQueueRunner.invoke(getTreeComponent(), "getRowBounds",
new Object[] { Integer.valueOf(row) }, new Class[] { Integer.TYPE });
if (bounds != null)
return bounds.getLocation();
return new Point(-1, -1);
}
public Dimension getSize() {
Rectangle bounds = (Rectangle) eventQueueRunner.invoke(getTreeComponent(), "getRowBounds",
new Object[] { Integer.valueOf(row) }, new Class[] { Integer.TYPE });
if (bounds != null)
return bounds.getSize();
return new Dimension(0, 0);
}
protected String getCollectionComponentAccessMethodName() {
return "getTree";
}
public MCollectionComponent getTree() {
return getCollectionComponentWithWindowID();
}
public int getRow() {
return row;
}
@Override
public String toString() {
return super.toString() + "[" + getText() + "]";
}
}