/*******************************************************************************
* Copyright (c) 2012 Google, Inc.
* 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:
* Google, Inc. - initial API and implementation
*******************************************************************************/
package com.windowtester.runtime.swt.internal.selector;
import java.awt.Point;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;
import abbot.finder.matchers.swt.TextMatcher;
import abbot.script.Condition;
import abbot.tester.swt.TreeTester;
import com.windowtester.internal.runtime.provisional.WTInternal;
import com.windowtester.runtime.MultipleWidgetsFoundException;
import com.windowtester.runtime.WT;
import com.windowtester.runtime.WidgetNotFoundException;
import com.windowtester.runtime.WidgetSearchException;
import com.windowtester.runtime.swt.internal.debug.LogHandler;
import com.windowtester.runtime.swt.internal.operation.SWTMouseOperation;
import com.windowtester.runtime.swt.internal.operation.SWTTreeItemLocation;
import com.windowtester.runtime.swt.internal.operation.SWTTreeItemOperation;
import com.windowtester.runtime.swt.internal.widgets.SWTWidgets;
import com.windowtester.runtime.swt.internal.widgets.TreeItemReference;
import com.windowtester.runtime.util.ScreenCapture;
import com.windowtester.swt.util.PathStringTokenizerUtil;
import com.windowtester.tester.swt.TreeItemTester;
/**
* A Selector for Tree Items.
*
* @author Phil Quitslund
*/
public class TreeItemSelector2 extends BasicWidgetSelector {
private TreeTester treeTester = new TreeTester();
private TreeItemTester treeItemTester = createTreeItemTester();
/**
* @see com.windowtester.event.swt.ISWTWidgetSelectorDelegate#clickExpand(org.eclipse.swt.widgets.Widget)
*/
public Widget clickExpand(Widget w) {
TreeItem item = (TreeItem)w;
Rectangle bounds = UIProxy.getBounds(item);
boolean isExpanded = UIProxy.getExpanded(item);
//TODO: solve this more robustly
int x = Math.round(bounds.x/4);
int y = Math.round(bounds.y/2);
click(w,-x,y, SWT.BUTTON1);
UIDriver.wait(new TreeItemExpandedCondition(item, !isExpanded), 20000);
return w;
}
//NOTE: override at will...
protected TreeItemTester createTreeItemTester() {
return new TreeItemTester();
}
/**
* @throws MultipleWidgetsFoundException
* @throws WidgetNotFoundException
* @see com.windowtester.event.swt.ISWTWidgetSelectorDelegate#click(org.eclipse.swt.widgets.Widget, java.lang.String, int)
*/
public Widget click(Widget w, String path, int mask) throws WidgetNotFoundException, MultipleWidgetsFoundException {
if (w instanceof Tree)
return click((Tree)w, path, mask);
//other cases?
return null;
}
public Widget click(int clicks, Tree tree, String path, int columnIndex, Point offset, int mask) throws WidgetSearchException {
// Widget clicked = null;
// try {
//find item
TreeItem item;
try {
item = atomicFind(tree, path);
}
catch (WidgetSearchException e) {
// [Dan] Can't figure out where the screen capture call was so add it here.
// Should probably rethink this when overhauling the selector classes
ScreenCapture.createScreenCapture();
throw e;
}
//reveal item
// treeTester.showItem(tree, item);
// showColumn(tree, columnIndex);
// it make sense to dispatch system events at this point
// waitForIdle(tree.getDisplay());
// if(SWT.getPlatform().equals("gtk")) // no harmful for Windows
// showItemExt(tree, item);
//click it
// Rectangle rect = (columnIndex == -1) ? UIProxy.getBounds(item) : UIProxy.getBounds(item, columnIndex);
// //w/ offset
// if (offset == null)
// offset = new Point(1, rect.height/2);
// if (columnIndex != -1) {
// offset.x += rect.x;
// }
// clicked = click (item, offset.x, offset.y, mask, clicks);
TreeItemReference itemRef = SWTWidgets.asReference(item);
SWTTreeItemOperation treeItemOp = new SWTTreeItemOperation(itemRef, columnIndex);
if ((mask & WT.CHECK) == WT.CHECK)
treeItemOp.checkSWTBotStyle();
treeItemOp.execute();
if ((mask & WT.BUTTON_MASK) != 0) {
SWTTreeItemLocation loc = new SWTTreeItemLocation(itemRef, WTInternal.TOPLEFT).column(columnIndex);
if (offset != null)
loc.offset(offset.x, offset.y);
else
loc.offset(5, 5);
new SWTMouseOperation(mask).at(loc).execute();
}
return item;
// } catch(IllegalStateException e) {
// //in this case, fall back on abbot:
// try {
// if (offset == null) {
// clicked = treeItemTester.actionClickTreeItem(path, tree, clicks);
// } else {
// //note we do the checking ourselves
// clicked = treeItemTester.actionClickTreeItem(path, abbot.tester.swt.TreeItemTester.DEFAULT_TREEITEM_PATH_DELIMITER, tree, TreeItemTester.NONCHECKABLE, offset.x, offset.y, clicks);
// }
//
// showColumn(tree, columnIndex);
// if (columnIndex != -1) {
// TreeItem item = (TreeItem)clicked;
// Rectangle rect = (columnIndex == -1) ? UIProxy.getBounds(item) : UIProxy.getBounds(item, columnIndex);
// //w/ offset
// if (offset == null)
// offset = new Point(1, rect.height/2);
// offset.x += rect.x;
// clicked = click (item, offset.x, offset.y, mask, clicks);
// }
//
//
//
// /*
// * Extra provision to ensure checks happen.
// * NOTE: This began as a fix for Linux but appears to be relevant in the win32 case
// * as well (com.collab.wt.smoke.tests.TreeCheckTest was failing)
// */
// if ((mask & SWT.CHECK) == SWT.CHECK) {
// setChecked(clicked);
// }
//
// } catch (abbot.finder.swt.WidgetNotFoundException e1) {
// throw new WidgetNotFoundException(e1.getMessage());
// } catch (abbot.finder.swt.MultipleWidgetsFoundException e1) {
// throw new MultipleWidgetsFoundException(e1);
// } catch(IllegalStateException e2) {
// LogHandler.log(e2);
// }
// }
// return clicked;
}
private TreeItem atomicFind(final Tree tree, final String path) throws WidgetNotFoundException {
// [Dan] Should not run on UI thread so that tree items can be properly expanded.
// final IllegalStateException caughtException[] = new IllegalStateException[1];
// TreeItem item = (TreeItem) DisplayExec.sync(new RunnableWithResult() {
// public Object runWithResult() {
// try {
return find(tree, path);
// } catch (IllegalStateException e) {
// caughtException[0] = e;
// }
// return null;
// }
// });
// if (caughtException[0] != null)
// throw caughtException[0];
// return item;
}
// private void showColumn(Tree tree, int columnIndex)
// throws WidgetSearchException {
// if (columnIndex != -1) {
// int columnCount = treeTester.getColumnCount(tree);
// if (columnIndex >= columnCount)
// throw new WidgetSearchException("Specified column (" + columnIndex +") out of range (count: " + columnCount + " columns)");
// TreeColumn column = treeTester.getColumn(tree, columnIndex);
// treeTester.showColumn(tree, column);
// }
// }
/**
* Click the Tree Item described by this path in the given tree/
* @param tree - the parent tree
* @param path - the path to the tree item in question
* @param mask - the mouse mask
* @return the clicked Tree Item
* @throws WidgetNotFoundException
* @throws MultipleWidgetsFoundException
*/
public Widget click(Tree tree, String path, int mask) throws WidgetNotFoundException, MultipleWidgetsFoundException {
Widget clicked = null;
try {
//find item
TreeItem item = atomicFind(tree, path);
//reveal item
treeTester.showItem(tree, item);
// it make sense to dispatch system events at this point
waitForIdle(tree.getDisplay());
if(SWT.getPlatform().equals("gtk")) // no harmful for Windows
showItemExt(tree, item);
//and click it
Rectangle rect = UIProxy.getBounds(item);
clicked = click (item, 1, rect.height/2, mask);
} catch(IllegalStateException e) {
//in this case, fall back on abbot:
try {
clicked = treeItemTester.actionClickTreeItem(path, tree, 1);
/*
* Extra provision to ensure checks happen.
* NOTE: This began as a fix for Linux but appears to be relevant in the win32 case
* as well (com.collab.wt.smoke.tests.TreeCheckTest was failing)
*/
if ((mask & SWT.CHECK) == SWT.CHECK) {
setChecked(clicked);
}
} catch (abbot.finder.swt.WidgetNotFoundException e1) {
throw new WidgetNotFoundException("Path: " + path + " not findable in tree " + UIProxy.getToString(tree));
} catch (abbot.finder.swt.MultipleWidgetsFoundException e1) {
throw new MultipleWidgetsFoundException("Path: " + path + " ambiguous in tree " + UIProxy.getToString(tree));
} catch(IllegalStateException e2) {
LogHandler.log(e2);
}
}
return clicked;
}
public Widget doubleClick(Widget w, String path, int mask) {
Tree tree = (Tree)w;
Widget clicked = null;
try {
//find item
TreeItem item;
try {
item = atomicFind(tree, path);
}
catch (WidgetNotFoundException e) {
throw new IllegalStateException(e);
}
//reveal item
treeTester.showItem(tree, item);
//it make sense to dispatch system events at this point
waitForIdle(tree.getDisplay());
if(SWT.getPlatform().equals("gtk")) // no harmful for Windows
showItemExt(tree, item);
//and click it
Rectangle rect = UIProxy.getBounds(item);
clicked = doubleClick (item, 1, rect.height/2, mask);
} catch(IllegalStateException e) {
//in this case, fall back on abbot:
try {
clicked = treeItemTester.actionClickTreeItem(path, tree, 2);
} catch (abbot.finder.swt.WidgetNotFoundException e1) {
throw new IllegalStateException("Path: " + path + " not findable in tree " + UIProxy.getToString(tree));
} catch (abbot.finder.swt.MultipleWidgetsFoundException e1) {
throw new IllegalStateException("Path: " + path + " ambiguous in tree " + UIProxy.getToString(tree));
} catch(IllegalStateException e2) {
LogHandler.log(e2);
}
}
return clicked;
}
/**
* @see com.windowtester.event.swt.ISWTWidgetSelectorDelegate#contextClick(org.eclipse.swt.widgets.Widget, java.lang.String, java.lang.String)
*/
// public Widget contextClick(Widget w, String itemPath, Point offset, String menuPath) throws WidgetNotFoundException, MultipleWidgetsFoundException {
// Tree tree = (Tree)w;
// Widget clicked = null;
// if (itemPath == null || itemPath.length() == 0)
// clicked = tree;
// else
// {
// try {
// //find item
// TreeItem item = atomicFind(tree, itemPath);
// //reveal item
// treeTester.showItem(tree, item);
// //it make sense to dispatch system events at this point
// waitForIdle(tree.getDisplay());
// clicked = item;
// } catch(IllegalStateException e) {
// //in this case, fall back on abbot:
// try {
// clicked = treeItemTester.getTreeItemByPath(itemPath, tree);
// } catch (abbot.finder.swt.WidgetNotFoundException e1) {
// //close menu in case of failure
// handleMenuClose();
// //04.03.07: this has been re-enabled -- the consequence might be that empty tree context clicks
// //are affected? (in any case, this is why Alex removed this bit...)
// //TODO: investigate context clicks on empty trees!
// throw new WidgetNotFoundException("Tree Item: " + itemPath + " not findable in tree " + UIProxy.getToString(tree));
// //clicked = tree;
// } catch (abbot.finder.swt.MultipleWidgetsFoundException e1) {
// //close menu in case of failure
// handleMenuClose();
// throw new MultipleWidgetsFoundException("Tree Item: " + itemPath + " ambiguous in tree " + UIProxy.getToString(tree));
// } catch(IllegalStateException e2) {
// LogHandler.log(e2);
// }
// }
// }
// if (offset != null)
// return contextClick(clicked, offset.x, offset.y, menuPath);
// return contextClick(clicked, menuPath);
// }
/**
* @see com.windowtester.event.swt.ISWTWidgetSelectorDelegate#contextClick(org.eclipse.swt.widgets.Widget, java.lang.String, java.lang.String)
*/
public Widget contextClick(Widget w, String itemPath, String menuPath) throws WidgetNotFoundException, MultipleWidgetsFoundException {
Tree tree = (Tree)w;
Widget clicked = null;
if (itemPath == null || itemPath.length() == 0)
clicked = tree;
else
{
try {
//find item
TreeItem item = atomicFind(tree, itemPath);
//reveal item
treeTester.showItem(tree, item);
//it make sense to dispatch system events at this point
waitForIdle(tree.getDisplay());
clicked = item;
} catch(IllegalStateException e) {
//in this case, fall back on abbot:
try {
clicked = treeItemTester.getTreeItemByPath(itemPath, tree);
} catch (abbot.finder.swt.WidgetNotFoundException e1) {
//close menu in case of failure
handleMenuClose();
//throw new WidgetNotFoundException("Tree Item: " + itemPath + " not findable in tree " + UIProxy.getToString(tree));
clicked = tree;
} catch (abbot.finder.swt.MultipleWidgetsFoundException e1) {
//close menu in case of failure
handleMenuClose();
throw new MultipleWidgetsFoundException("Tree Item: " + itemPath + " ambiguous in tree " + UIProxy.getToString(tree));
} catch(IllegalStateException e2) {
LogHandler.log(e2);
}
}
}
return contextClick(clicked, menuPath);
}
/**
* @see com.windowtester.event.swt.ISWTWidgetSelectorDelegate#selectAll(org.eclipse.swt.widgets.Widget)
*/
public void selectAll(final Widget w) {
w.getDisplay().syncExec(new Runnable() {
public void run() {
((Tree)w).selectAll();
}
});
}
/**
* Find the Tree Item described by this path String.
* @param tree - the containing tree
* @param path - the path describing the item in question
* @return the found Tree Item
*/
private TreeItem find(Tree tree, String path) throws WidgetNotFoundException {
//Fixing to handle escaped '\'s
//String[] nodeLabels = path.split(DELIM);
String[] nodeLabels = PathStringTokenizerUtil.tokenize(path);
TreeItem[] items;
TreeItem node = null;
TreeItem current = null;
items = treeTester.getItems(tree);
//for each node label
int i = 0;
while (true) {
String nodeLabel = nodeLabels[i];
node = null;
//find the appropriate item
for (int j = 0; j < items.length && node == null; j++) {
current = items[j];
if (new TextMatcher(nodeLabel).matches(current)) {
node = current;
break;
}
}
if (node == null) {
throw new WidgetNotFoundException("Failed to find match for \"" + nodeLabel + "\" in path " + path);
}
if (++i >= nodeLabels.length)
break;
// get its children... but do not call for leaf nodes
treeItemTester.actionExpandItem(node);
items = treeItemTester.getItems(node);
}
return node;
}
////////////////////////////////////////////////////////////////////////////
//
// Wait condition helpers
//
////////////////////////////////////////////////////////////////////////////
/**
* A condition that waits for an expanded (true or false) condition.
* @author Phil Quitslund
*/
public static class TreeItemExpandedCondition implements Condition {
private final TreeItem _treeNode;
private final boolean _isExpanded;
/**
* Create an instance.
* @param treeNode - the node to wait on
* @param isExpanded - whether the node should be expanded or not
*/
public TreeItemExpandedCondition(TreeItem treeNode, boolean isExpanded) {
_treeNode = treeNode;
_isExpanded = isExpanded;
}
/**
* @see abbot.script.Condition#test()
*/
public boolean test() {
return UIProxy.getExpanded(_treeNode) == _isExpanded;
}
}
public void showItemExt(final Tree t, final TreeItem item) {
if (t == null || item == null)
return;
Rectangle itemRect = treeItemTester.getBounds(item);
setValueToScrollBar(treeTester.getHorizontalBar(t), itemRect.x);
}
public void setValueToScrollBar(final ScrollBar bar, final int value)
{
bar.getDisplay().syncExec(new Runnable() {
public void run() {
bar.setSelection(value);
waitForIdle(bar.getDisplay());
}
});
}
}