/******************************************************************************* * Copyright (c) 2013 BREDEX GmbH. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * BREDEX GmbH - initial API and implementation and/or initial documentation *******************************************************************************/ package org.eclipse.jubula.rc.javafx.tester.util; import java.awt.Rectangle; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; import org.apache.commons.lang.ObjectUtils; import org.eclipse.jubula.rc.common.adaptable.AdapterFactoryRegistry; import org.eclipse.jubula.rc.common.driver.ClickOptions; import org.eclipse.jubula.rc.common.driver.DragAndDropHelper; import org.eclipse.jubula.rc.common.driver.IEventThreadQueuer; import org.eclipse.jubula.rc.common.driver.IRobot; import org.eclipse.jubula.rc.common.exception.StepExecutionException; import org.eclipse.jubula.rc.common.implclasses.tree.AbstractTreeOperationContext; import org.eclipse.jubula.rc.common.logger.AutServerLogger; import org.eclipse.jubula.rc.common.tester.adapter.interfaces.IComponent; import org.eclipse.jubula.rc.common.tester.adapter.interfaces.ITextComponent; import org.eclipse.jubula.rc.common.util.SelectionUtil; import org.eclipse.jubula.rc.javafx.driver.EventThreadQueuerJavaFXImpl; import org.eclipse.jubula.tools.internal.objects.event.EventFactory; import org.eclipse.jubula.tools.internal.objects.event.TestErrorEvent; import javafx.collections.ObservableList; import javafx.scene.control.TreeCell; import javafx.scene.control.TreeItem; import javafx.scene.control.TreeView; /** * This context holds the tree and supports access to the Robot. It also * implements some general operations on trees. * * @author BREDEX GmbH */ public class TreeOperationContext extends AbstractTreeOperationContext<TreeView<?>, TreeItem<?>> { /** The AUT Server logger. */ private static AutServerLogger log = new AutServerLogger( TreeOperationContext.class); /** * Creates a new instance. The JTree must have a tree model. * * @param queuer * the queuer * @param robot * the Robot * @param tree * the tree */ public TreeOperationContext(IEventThreadQueuer queuer, IRobot robot, TreeView<?> tree) { super(queuer, robot, tree); if (tree.getRoot() == null) { throw new StepExecutionException( "Tree is empty.", //$NON-NLS-1$ EventFactory.createActionError(TestErrorEvent.NOT_FOUND)); } } /** * {@inheritDoc} * * @param row * Not used! */ @Override protected String convertValueToText(final TreeItem<?> node, final int row) throws StepExecutionException { String result = EventThreadQueuerJavaFXImpl.invokeAndWait( "convertValueToText", new Callable<String>() { //$NON-NLS-1$ @Override public String call() throws Exception { if (node != null) { Object val = node.getValue(); if (val != null) { return val.toString(); } } return null; } }); return result; } @Override public Collection<String> getNodeTextList(TreeItem<?> node) { List<String> res = new ArrayList<String>(); int rowNotUsed = 0; String valText = convertValueToText(node, rowNotUsed); if (valText != null) { res.add(valText); } String rendText = getRenderedText(node); if (rendText != null) { res.add(rendText); } return res; } @Override public String getRenderedText(final TreeItem<?> node) throws StepExecutionException { String result = EventThreadQueuerJavaFXImpl.invokeAndWait( "getRenderedText", new Callable<String>() { //$NON-NLS-1$ @Override public String call() throws Exception { TreeCell cell = getCellForNode(node); if (cell != null) { IComponent adapter = (IComponent) AdapterFactoryRegistry.getInstance() .getAdapter(IComponent.class, cell); if (adapter != null && adapter instanceof ITextComponent) { return ((ITextComponent) adapter).getText(); } } return null; } }); return result; } /** * Get the actual TreeCell for a given TreeItem * @param node the item * @return the cell or null if no cell was found */ public TreeCell getCellForNode(final TreeItem<?> node) { scrollNodeToVisible(node); TreeView<?> tree = getTree(); // Update the layout coordinates otherwise // we would get old position values tree.layout(); List<? extends TreeCell> tCells = NodeTraverseHelper .getInstancesOf(tree, TreeCell.class); for (TreeCell<?> cell : tCells) { TreeItem<?> item = cell.getTreeItem(); if (NodeTraverseHelper.isVisible(cell) && item != null && item.equals(node)) { return cell; } } return null; } @Override public boolean isVisible(final TreeItem<?> node) { return EventThreadQueuerJavaFXImpl.invokeAndWait("isVisible", //$NON-NLS-1$ new Callable<Boolean>() { @Override public Boolean call() throws Exception { TreeItem<?> parent = node.getParent(); if (parent != null) { return parent.isExpanded() && getTree().isVisible(); } return getTree().isVisible(); } }); } @Override public Rectangle getVisibleRowBounds(final Rectangle rowBounds) { Rectangle result = EventThreadQueuerJavaFXImpl.invokeAndWait( "getVisibleRowBounds", new Callable<Rectangle>() { //$NON-NLS-1$ @Override public Rectangle call() throws Exception { TreeView<?> tree = getTree(); // Update the layout coordinates otherwise // we would get old position values tree.layout(); Rectangle visibleTreeBounds = new Rectangle(0, 0, Rounding.round(tree.getWidth()), Rounding .round(tree.getHeight())); return rowBounds.intersection(visibleTreeBounds); } }); return result; } @Override public void scrollNodeToVisible(final TreeItem<?> node) { EventThreadQueuerJavaFXImpl.invokeAndWait("scrollNodeToVisible", //$NON-NLS-1$ new Callable<Void>() { @Override public Void call() throws Exception { TreeView<?> tree = getTree(); int index = ((TreeView) tree).getRow(node); tree.scrollTo(index); // Update the layout coordinates otherwise // we would get old position values tree.layout(); return null; } }); } @Override public void clickNode(TreeItem<?> node, ClickOptions clickOps) { scrollNodeToVisible(node); Rectangle rowBounds = getNodeBounds(node); Rectangle visibleRowBounds = getVisibleRowBounds(rowBounds); getRobot().click(getTree(), visibleRowBounds, clickOps); } @Override public void expandNode(final TreeItem<?> node) { scrollNodeToVisible(node); boolean expanded = EventThreadQueuerJavaFXImpl.invokeAndWait( "expandNodeCheckIfExpanded", //$NON-NLS-1$ new Callable<Boolean>() { @Override public Boolean call() throws Exception { return node.isExpanded(); } }); if (expanded) { return; } // If this is called during drag mode the target is not visible if (DragAndDropHelper.getInstance().isDragMode()) { throw new StepExecutionException("Drop target not visible", //$NON-NLS-1$ EventFactory.createActionError(TestErrorEvent.NOT_VISIBLE)); } Object result = EventThreadQueuerJavaFXImpl.invokeAndWait("expandNode", //$NON-NLS-1$ new Callable<Object>() { @Override public Object call() throws Exception { TreeView<?> tree = getTree(); // Update the layout coordinates otherwise // we would get old position values tree.layout(); List<? extends TreeCell> tCells = NodeTraverseHelper .getInstancesOf(tree, TreeCell.class); for (TreeCell<?> cell : tCells) { TreeItem<?> item = cell.getTreeItem(); if (NodeTraverseHelper.isVisible(cell) && item != null && item.equals(node) && !item.isExpanded()) { return cell.getDisclosureNode(); } } return null; } }); if (result != null) { getRobot().click(result, null, ClickOptions.create().setClickCount(1).setMouseButton(1)); } EventThreadQueuerJavaFXImpl.invokeAndWait("expandNodeCheckIfExpanded", //$NON-NLS-1$ new Callable<Void>() { @Override public Void call() throws Exception { if (!getTree().isDisabled() && !node.isExpanded()) { log.warn("Expand node fallback used for: " //$NON-NLS-1$ + node.getValue()); node.setExpanded(true); } return null; } }); } @Override public void collapseNode(final TreeItem<?> node) { scrollNodeToVisible(node); Object result = EventThreadQueuerJavaFXImpl.invokeAndWait( "collapseNode", new Callable<Object>() { //$NON-NLS-1$ @Override public Object call() throws Exception { TreeView<?> tree = getTree(); // Update the layout coordinates otherwise // we would get old position values tree.layout(); List<? extends TreeCell> tCells = NodeTraverseHelper .getInstancesOf(tree, TreeCell.class); for (TreeCell<?> cell : tCells) { TreeItem<?> item = cell.getTreeItem(); if (NodeTraverseHelper.isVisible(cell) && item != null && item.equals(node) && item.isExpanded()) { return cell.getDisclosureNode(); } } return null; } }); if (result != null) { getRobot().click(result, null, ClickOptions.create().setClickCount(1).setMouseButton(1)); } EventThreadQueuerJavaFXImpl.invokeAndWait( "collapseNodeCheckIfCollapsed", new Callable<Void>() { //$NON-NLS-1$ @Override public Void call() throws Exception { if (!getTree().isDisabled() && node.isExpanded()) { log.warn("Collapse node fallback used for: " //$NON-NLS-1$ + node.getValue()); node.setExpanded(false); } return null; } }); } @Override public TreeItem<?> getSelectedNode() { TreeItem<?> result = EventThreadQueuerJavaFXImpl.invokeAndWait( "getSelectedNode", new Callable<TreeItem<?>>() { //$NON-NLS-1$ @Override public TreeItem<?> call() throws Exception { return getTree().getSelectionModel().getSelectedItem(); } }); if (result != null) { SelectionUtil.validateSelection(new Object[] { result }); } else { SelectionUtil.validateSelection(new Object[] {}); } return result; } @Override public TreeItem<?>[] getSelectedNodes() { TreeItem<?>[] result = EventThreadQueuerJavaFXImpl.invokeAndWait( "getSelectedNode", new Callable<TreeItem<?>[]>() { //$NON-NLS-1$ @Override public TreeItem<?>[] call() throws Exception { ObservableList<?> selectedItems = getTree() .getSelectionModel().getSelectedItems(); return selectedItems.toArray(new TreeItem[ selectedItems.size()]); } }); SelectionUtil.validateSelection(result); return result; } @Override public TreeItem<?>[] getRootNodes() { return EventThreadQueuerJavaFXImpl.invokeAndWait( "getRootNodes", new Callable<TreeItem<?>[]>() { //$NON-NLS-1$ @Override public TreeItem<?>[] call() throws Exception { TreeView<?> tree = getTree(); // If the root is visible, just return that. if (tree.showRootProperty().getValue()) { return new TreeItem[] { tree.getRoot() }; } // If the root is not visible, return all direct // children of the // non-visible root. return getChildren(tree.getRoot()); } }); } @Override public TreeItem<?> getParent(final TreeItem<?> child) { return EventThreadQueuerJavaFXImpl.invokeAndWait("getParent", //$NON-NLS-1$ new Callable<TreeItem<?>>() { @Override public TreeItem<?> call() throws Exception { return child.getParent(); } }); } @Override public TreeItem<?> getChild(final TreeItem<?> parent, final int index) { return EventThreadQueuerJavaFXImpl.invokeAndWait("getChild", //$NON-NLS-1$ new Callable<TreeItem<?>>() { @Override public TreeItem<?> call() throws Exception { return parent.getChildren().get(index); } }); } @Override public int getNumberOfChildren(final TreeItem<?> parent) { return EventThreadQueuerJavaFXImpl.invokeAndWait( "getNumberOfChildren", new Callable<Integer>() { //$NON-NLS-1$ @Override public Integer call() throws Exception { return parent.getChildren().size(); } }); } @Override public boolean isLeaf(final TreeItem<?> node) { return EventThreadQueuerJavaFXImpl.invokeAndWait("isLeaf", //$NON-NLS-1$ new Callable<Boolean>() { @Override public Boolean call() throws Exception { return node.isLeaf(); } }); } @Override public TreeItem<?>[] getChildren(final TreeItem<?> parent) { return EventThreadQueuerJavaFXImpl.invokeAndWait( "getChildren", new Callable<TreeItem<?>[]>() { //$NON-NLS-1$ @Override public TreeItem<?>[] call() throws Exception { ObservableList<?> children = parent.getChildren(); return children.toArray(new TreeItem[children.size()]); } }); } @Override public Rectangle getNodeBounds(final TreeItem<?> node) { Rectangle result = EventThreadQueuerJavaFXImpl.invokeAndWait( "getNodeBounds", new Callable<Rectangle>() { //$NON-NLS-1$ @Override public Rectangle call() throws Exception { TreeView<?> tree = getTree(); // Update the layout coordinates otherwise // we would get old position values tree.layout(); List<? extends TreeCell> tCells = NodeTraverseHelper .getInstancesOf(tree, TreeCell.class); for (TreeCell<?> cell : tCells) { TreeItem<?> item = cell.getTreeItem(); if (NodeTraverseHelper.isVisible(cell) && (item != null && item.equals(node))) { Rectangle b = NodeBounds .getAbsoluteBounds(cell); Rectangle treeB = NodeBounds .getAbsoluteBounds(tree); return new Rectangle( Math.abs(treeB.x - b.x), Math.abs(treeB.y - b.y), Rounding.round(b.getWidth()), Rounding.round(b.getHeight())); } } return null; } }); if (result == null) { throw new StepExecutionException( "Could not retrieve visible node bounds.", //$NON-NLS-1$ EventFactory.createActionError(TestErrorEvent.NOT_VISIBLE)); } return result; } @Override public int getIndexOfChild(final TreeItem<?> parent, final TreeItem<?> child) { return EventThreadQueuerJavaFXImpl.invokeAndWait( "getIndexOfChild", new Callable<Integer>() { //$NON-NLS-1$ @Override public Integer call() throws Exception { if (parent == null) { Object[] rootNodes = getRootNodes(); for (int i = 0; i < rootNodes.length; i++) { if (ObjectUtils.equals(rootNodes[i], child)) { return i; } } return -1; } List<?> children = parent.getChildren(); if (children.contains(child)) { return children.indexOf(child); } return -1; } }); } }