/* * Copyright (c) 2009-2013, 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. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * 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.fx.control; import javafx.scene.control.skin.VirtualFlow; import javafx.scene.Node; import javafx.scene.control.ScrollBar; import javafx.scene.control.TreeCell; import javafx.scene.control.TreeItem; import javafx.scene.control.TreeView; import org.jemmy.action.FutureAction; import org.jemmy.control.As; import org.jemmy.control.ControlInterfaces; import org.jemmy.control.ControlType; import org.jemmy.control.Property; import org.jemmy.env.Environment; import org.jemmy.fx.control.Scrollable2DImpl.ScrollsLookupCriteria; import org.jemmy.interfaces.*; import org.jemmy.lookup.ByStringLookup; import org.jemmy.resources.StringComparePolicy; import org.jemmy.timing.DescriptiveLookupCriteria; import java.util.List; /** * Tree support in JemmyFX is provided through a few different control * interfaces. Namely, these are * Tree, * Parent and * Selectable. A tree could be considered a parent/selectable for * two types of objects: * javafx.scene.control.TreeItem in which case TreeItems are * themselves the elements of the hierarchy/list or the underlying data held * within the tree items. * * @param * @author shura * @see #asTreeItemParent() * @see #asTreeItemSelectable() * @see #asSelectable(java.lang.Class) * @see #asItemParent(java.lang.Class) * @see TreeViewDock */ @ControlType({TreeView.class}) @ControlInterfaces(value = {Selectable.class, EditableCellOwner.class, Tree.class, Selectable.class, Scroll.class, Scrollable2D.class}, encapsulates = {TreeItem.class, Object.class, Object.class, Object.class}, name = {"asTreeItemSelectable", "asItemParent"}) public class TreeViewWrap extends ControlWrap implements Scroll, Selectable, Focusable { public static final String SELECTED_INDEX_PROP_NAME = "tree.selected.index"; public static final String SELECTED_ITEM_PROP_NAME = "tree.selected.item"; public static final String SHOW_ROOT_PROP_NAME = "show.root"; public static final String ROOT_PROP_NAME = "root.item"; private TableTreeScroll scroll; private Scrollable2D scrollable2D; private Selectable selectable; private Selectable itemSelectable; private TreeItemParent parent; private TreeNodeParent itemParent; private Tree tree; /** * @param env * @param scene * @param nd */ @SuppressWarnings("unchecked") public TreeViewWrap(Environment env, CONTROL nd) { super(env, nd); } /** * This method finds TreeCell for the selected item. Should be invoked only * using FX.deferAction() That can be needed for cases like obtaining * screenBounds for corresponding TreeCell. * * @return TreeCell, null if it is not visible */ @SuppressWarnings("unchecked") TreeCell getTreeCell(final TreeItem item) { return (TreeCell) as(Parent.class, Node.class).lookup(TreeCell.class, new DescriptiveLookupCriteria(cell -> { if (cell.isVisible() && cell.getOpacity() == 1.0) { if (cell.getTreeItem() == item) { return true; } } return false; }, () -> "Looking for a visible treeCell with the value '" + item + "'")).get(0); } int getRow(final TreeItem item) { return new FutureAction<>(getEnvironment(), () -> getControl().getRow(item)).get(); } /** * Allows to work with tree as with a list on selectable data objects - the * objects which are accessible through * javafx.scene.control.TreeItem.getValue(). Notice that only * expanded tree items get into the list. A set of items in the hierarchy is * also limited by the type parameter - objects of other types do not make * it to the list. * * @param * @param type * @return * @see #asTreeItemSelectable() */ @As(Object.class) public Selectable asSelectable(Class type) { if (selectable == null || !selectable.getType().equals(type)) { selectable = new TreeViewSelectable<>(type); } return selectable; } /** * Allows to work with tree as with a list on selectable items. Notice that * only expanded tree items get into the list. * * @return * @see #asSelectable(java.lang.Class) */ @As(TreeItem.class) public Selectable asTreeItemSelectable() { if (itemSelectable == null) { itemSelectable = new TreeViewSelectable(); } return itemSelectable; } /** * Allows to perform lookup for * javafx.scene.control.TreeItems. All objects within the tree * are elements of this hierarchy: whether visible or not. Notice though * that the implementation does not expand nodes, so if a tree is loaded * dynamically on node expand, those dynamically added nodes will not be a * part of hierarchy. * * @param * @param type * @return * @see #asTreeItemParent() */ @As(TreeItem.class) public EditableCellOwner asTreeItemParent(Class type) { if (itemParent == null) { itemParent = new TreeNodeParent<>(this, type); } return itemParent; } /** * Allows to perform lookup for data objects - the objects which are * accessible through * javafx.scene.control.TreeItem.getValue(). All objects within * the tree are elements of this hierarchy: whether visible or not. Notice * though that the implementation does not expand nodes, so if a tree is * loaded dynamically on node expand, those dynamically added nodes will not * be a part of hierarchy. * * @param type of data supported by the tree. If should be consistent * with the data present in the tree, because the tree data may get casted * to this type parameter during lookup operations. That, this must be a * super type for all types present in the tree. * @param type * @return * @see #asItemParent() */ @As(Object.class) public EditableCellOwner asItemParent(Class type) { if (parent == null || !parent.getType().equals(type)) { parent = new TreeItemParent<>(this, type); } return parent; } /** * Allows selections of paths. Notice that in the tree implementation, tree * root is not looked up. That is, fist lookup criterion of the criteria * passed for tree selection will be used for finding a child of the root * node whether or not the root node is shown. * * @param * @param type * @return */ @As(Object.class) public Tree asTree(Class type) { if (tree == null || !tree.getType().equals(type)) { asItemParent(type); tree = new TreeImpl<>(type, this, parent); } return tree; } /** * Returns selected row index. * * @return */ @Property(SELECTED_INDEX_PROP_NAME) public int getSelectedIndex() { return new FutureAction<>(getEnvironment(), () -> getControl().getSelectionModel().getSelectedIndex()).get(); } @Property(SELECTED_ITEM_PROP_NAME) public TreeItem getSelectedItem() { return new FutureAction<>(getEnvironment(), () -> (TreeItem) getControl().getSelectionModel().getSelectedItem()).get(); } @Override public List getStates() { return asTreeItemSelectable().getStates(); } @Override public TreeItem getState() { return asTreeItemSelectable().getState(); } @Override public Selector selector() { return asTreeItemSelectable().selector(); } @Override public Class getType() { return TreeItem.class; } @Property(SHOW_ROOT_PROP_NAME) public boolean isShowRoot() { return new FutureAction<>(getEnvironment(), () -> getControl().isShowRoot()).get(); } @Property(ROOT_PROP_NAME) public TreeItem getRoot() { return new FutureAction<>(getEnvironment(), () -> getControl().getRoot()).get(); } @As public Scrollable2D asScrollable2D() { if (scrollable2D == null) { scrollable2D = new Scrollable2DImpl(this, new ScrollsLookupCriteria() { @Override public boolean checkFor(ScrollBar scrollBar) { return ((scrollBar.getParent() instanceof VirtualFlow) && (scrollBar.getParent().getParent() instanceof TreeView)); } }); } return scrollable2D; } @As public Scroll asScroll() { checkScroll(); return scroll.asScroll(); } @Property(VALUE_PROP_NAME) public double position() { checkScroll(); return scroll.position(); } @Property(MINIMUM_PROP_NAME) public double minimum() { checkScroll(); return scroll.minimum(); } @Property(MAXIMUM_PROP_NAME) public double maximum() { checkScroll(); return scroll.maximum(); } @Override @Deprecated public double value() { checkScroll(); return position(); } @Deprecated public Scroller scroller() { checkScroll(); return scroll.scroller(); } public Caret caret() { checkScroll(); return scroll.caret(); } public void to(double position) { checkScroll(); scroll.to(position); } private void checkScroll() { if (scroll == null) { scroll = new TableTreeScroll(this); } } public static class ByTestTreeItem extends ByStringLookup { public ByTestTreeItem(String text, StringComparePolicy policy) { super(text, policy); } @Override public String getText(T t) { return t.getValue().toString(); } } protected class TreeViewSelectable extends TreeSelectable { private TreeNodeParent treeNodeParent; TreeViewSelectable(Class type) { super(type); } TreeViewSelectable() { super(); } @Override protected TreeItem getRoot() { return TreeViewWrap.this.getRoot(); } @Override protected boolean isShowRoot() { return TreeViewWrap.this.isShowRoot(); } @Override protected TreeItem getSelectedItem() { return TreeViewWrap.this.getSelectedItem(); } @Override protected Parent asTreeItemParent() { if (treeNodeParent == null) { treeNodeParent = new TreeNodeParent(TreeViewWrap.this, TreeItem.class); } return treeNodeParent; } @Override protected Environment getEnvironment() { return TreeViewWrap.this.getEnvironment(); } } }