/* * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code 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 General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.jemmy.swt; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeItem; import org.jemmy.TimeoutExpiredException; import org.jemmy.action.GetAction; import org.jemmy.env.Timeout; import org.jemmy.interfaces.Focusable; import org.jemmy.interfaces.Keyboard.KeyboardButton; import org.jemmy.interfaces.Keyboard.KeyboardButtons; import org.jemmy.interfaces.Keyboard.KeyboardModifier; import org.jemmy.resources.StringComparePolicy; import org.jemmy.swt.lookup.ByItemStringsLookup; import org.jemmy.timing.State; /** * @author shura, erikgreijus */ public class SWTTree extends StringTree { public static final Timeout WAIT_NODE_TIMEOUT = new Timeout(SWTTree.class.getName() + ".wait.node.timeout", 1000); public static final Timeout WAIT_NODE_EXPANDED_TIMEOUT = new Timeout(SWTTree.class.getName() + ".wait.node.expanded.timeout", 100); public static final Timeout AFTER_MOVE_SLEEP = new Timeout(SWTTree.class.getName() + ".after.move.sleep.timeout", 100); public static final String EXPAND_BUTTON_PROP = SWTTree.class.getName() + ".expand.button"; public static final String EXPAND_MODIFIER_PROP = SWTTree.class.getName() + ".expand.modifier"; private static final int MAX_MOVE_TRIES = 5; static { Shells.SHELLS.getEnvironment().initTimeout(WAIT_NODE_TIMEOUT); Shells.SHELLS.getEnvironment().initTimeout(WAIT_NODE_EXPANDED_TIMEOUT); Shells.SHELLS.getEnvironment().initTimeout(AFTER_MOVE_SLEEP); } TreeWrap owner; SWTTreeSelector selector = null; private final KeyboardButton expandButton; private final KeyboardModifier[] expandModifier; public SWTTree(TreeWrap owner) { super(owner.getEnvironment()); this.owner = owner; KeyboardButton defaultExpandButton = System.getProperty("os.name") .contains("Linux") ? KeyboardButtons.ADD : KeyboardButtons.RIGHT; KeyboardModifier[] defaultExpandModifier = new KeyboardModifier[0]; expandButton = (KeyboardButton) owner.getEnvironment().getProperty(KeyboardButtons.class, EXPAND_BUTTON_PROP, defaultExpandButton); expandModifier = (KeyboardModifier[]) owner.getEnvironment().getProperty(KeyboardModifier[].class, EXPAND_MODIFIER_PROP, defaultExpandModifier); } @Override protected LookupCriteria createCriteria(String text, StringComparePolicy policy) { return new ByItemStringsLookup<>(text, policy); } @Override public TreeSelector selector() { if (selector == null) { selector = new SWTTreeSelector(); } return selector; } @Override public Class getType() { return TreeItem.class; } private boolean getExpanded(final TreeItem item) { return new GetAction() { @Override public void run(Object... parameters) throws Exception { setResult(item.getExpanded()); } }.dispatch(owner.getEnvironment()); } private class SWTTreeSelector implements TreeSelector { private List> criteriaList; @Override public Wrap select(LookupCriteria... criteria) { criteriaList = Arrays.asList(criteria); owner.as(Focusable.class).focuser().focus(); TreeItem[] items = new GetAction() { @Override public void run(Object... parameters) throws Exception { setResult(owner.getControl().getItems()); } }.dispatch(owner.getEnvironment()); return new ItemWrap<>(owner, waitAndExpand(Arrays.asList(items), Arrays.asList(criteria))); } protected TreeItem waitAndExpand(final List items, final List> criteria) { final TreeItem next = owner.getEnvironment().getWaiter(WAIT_NODE_TIMEOUT).ensureState(new State() { @Override public TreeItem reached() { for (TreeItem ti : items) { if (criteria.get(0).check(ti)) { return ti; } } return null; } @Override public String toString() { return getReadableCriteriaList(); } }); int numberOfTries = 0; // determine if we really need to walk the tree to this non-leaf node (and expand it) in order to be able to find the next node boolean isExpanded = criteria.size() > 1 && getExpanded(next); while (!next.equals(owner.getSelectedItem()) && !isExpanded) { int from = owner.getItems().indexOf(owner.getSelectedItem()); int to = owner.getItems().indexOf(next); if (numberOfTries > 0) { if (from == -1) { /* in case we were unable to select the desired item, * focus (but not selection) is likely on the last or the * only item in the tree so pushing the down or up key won't select the item. */ owner.keyboard().pushKey(KeyboardButtons.SPACE); owner.getEnvironment().getTimeout(AFTER_MOVE_SLEEP).sleep(); } else if (numberOfTries > 1) { // we could be stuck on an editable field so keyboard navigation won't work until we escape out // of that. Only do this if the first retry round fails as pushing escape may close a crucial dialog owner.keyboard().pushKey(KeyboardButtons.ESCAPE); owner.getEnvironment().getTimeout(AFTER_MOVE_SLEEP).sleep(); } from = owner.getItems().indexOf(owner.getSelectedItem()); } KeyboardButton btt = (to > from) ? KeyboardButtons.DOWN : KeyboardButtons.UP; for (int i = 0; i < Math.abs(to - from); i++) { owner.keyboard().pushKey(btt); owner.getEnvironment().getTimeout(AFTER_MOVE_SLEEP).sleep(); } numberOfTries++; if (numberOfTries >= MAX_MOVE_TRIES) { if (!next.equals(owner.getSelectedItem())) { throw new TimeoutExpiredException("Unable to select the tree item with the following path " + getReadableCriteriaList()); } else { break; } } } if (criteria.size() > 1) { if (!getExpanded(next)) { owner.keyboard().pushKey(expandButton, expandModifier); owner.getEnvironment().getTimeout(AFTER_MOVE_SLEEP).sleep(); owner.keyboard().pushKey(KeyboardButtons.DOWN); owner.getEnvironment().getTimeout(AFTER_MOVE_SLEEP).sleep(); } owner.getEnvironment().getWaiter(WAIT_NODE_EXPANDED_TIMEOUT). ensureValue(true, new State() { @Override public Boolean reached() { return getExpanded(next); } }); return waitAndExpand(getItems(next), criteria.subList(1, criteria.size())); } else { return next; } } protected List getItems(final TreeItem next) { return new GetAction>() { @Override public void run(Object... parameters) throws Exception { setResult(Arrays.asList(next.getItems())); } }.dispatch(owner.getEnvironment()); } private String getReadableCriteriaList() { List result = new ArrayList<>(); criteriaList.stream().forEach((lookupCriteria) -> { result.add(lookupCriteria.toString()); }); return result.toString(); } } private LookupCriteria thisCriteria(final TreeItem item) { return new LookupCriteria() { @Override public boolean check(TreeItem control) { return control == item; } }; } }