1 /* 2 * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.javafx.scene.traversal; 27 28 import com.sun.javafx.application.PlatformImpl; 29 import com.sun.javafx.scene.NodeHelper; 30 import javafx.geometry.BoundingBox; 31 import javafx.geometry.Bounds; 32 import javafx.scene.Node; 33 import javafx.scene.Parent; 34 35 import java.util.ArrayList; 36 import java.util.List; 37 38 /** 39 * This is abstract class for a traversal engine. There are 2 types : {@link com.sun.javafx.scene.traversal.ParentTraversalEngine} 40 * to be used in {@link Parent#setTraversalEngine(ParentTraversalEngine)} to override default behavior 41 * and {@link com.sun.javafx.scene.traversal.TopMostTraversalEngine} that is the default traversal engine for scene and subscene. 42 * 43 * Every engine is basically a wrapper of an algorithm + some specific parent (or scene/subscene), which define engine's root. 44 */ 45 public abstract class TraversalEngine{ 46 47 /** 48 * This is the default algorithm for the running platform. It's the algorithm that's used in TopMostTraversalEngine 49 */ 50 static final Algorithm DEFAULT_ALGORITHM = PlatformImpl.isContextual2DNavigation() ? new Hueristic2D() : new ContainerTabOrder(); 51 52 private final TraversalContext context = new EngineContext(); // This is the context used in calls to this engine's algorithm 53 // This is a special context that's used when invoking select "callbacks" to default algorithm in other contexts 54 private final TempEngineContext tempEngineContext = new TempEngineContext(); 55 protected final Algorithm algorithm; 56 57 private final Bounds initialBounds = new BoundingBox(0, 0, 1, 1); 58 private final ArrayList<TraverseListener> listeners = new ArrayList<>(); 59 60 /** 61 * Creates engine with the specified algorithm 62 * @param algorithm 63 */ 64 protected TraversalEngine(Algorithm algorithm) { 65 this.algorithm = algorithm; 66 } 67 68 /** 69 * Creates engine with no algorithm. This makes all the select* calls invalid. 70 * @see #canTraverse() 71 */ 72 protected TraversalEngine() { 73 this.algorithm = null; 74 } 75 76 /** 77 * Add a listener to traversal engine. The listener is notified whenever focus is changed by traversal inside the associated scene or parent. 78 * This can be used with ParentTraversalEngine that has no algorithm to observe changes to the focus inside the parent. 79 * @param listener 80 */ 81 public final void addTraverseListener(TraverseListener listener) { 82 listeners.add(listener); 83 } 84 85 /** 86 * Fire notifications for listeners. This is called from the TopMostTraversalEngine 87 * @param newNode the node which has been focused 88 */ 89 final void notifyTraversedTo(Node newNode) { 90 for (TraverseListener l : listeners) { 91 l.onTraverse(newNode, getLayoutBounds(newNode, getRoot())); 92 } 93 } 94 95 /** 96 * Returns the node that is in the direction {@code dir} starting from the Node {@code from} using the engine's algorithm. 97 * Null means there is no Node in that direction 98 * @param from the node to start traversal from 99 * @param dir the direction of traversal 100 * @return the subsequent node in the specified direction or null if none 101 * @throws java.lang.NullPointerException if there is no algorithm 102 */ 103 public final Node select(Node from, Direction dir) { 104 return algorithm.select(from, dir, context); 105 } 106 107 /** 108 * Returns the first node in this engine's context (scene/parent) using the engine's algorithm. 109 * This can be null only if there are no traversable nodes 110 * @return The first node or null if none exists 111 * @throws java.lang.NullPointerException if there is no algorithm 112 */ 113 public final Node selectFirst() { 114 return algorithm.selectFirst(context); 115 } 116 117 /** 118 * Returns the last node in this engine's context (scene/parent) using the engine's algorithm. 119 * This can be null only if there are no traversable nodes 120 * @return The last node or null if none exists 121 * @throws java.lang.NullPointerException if there is no algorithm 122 */ 123 public final Node selectLast() { 124 return algorithm.selectLast(context); 125 } 126 127 /** 128 * The root of this engine's context. This is the node that is the root of the tree that is traversed by this engine. 129 * @return This engine's root 130 */ 131 protected abstract Parent getRoot(); 132 133 /** 134 * Returns true only if there's specified algorithm for this engine. Otherwise, this engine cannot be used for traversal. 135 * The engine might be still useful however, e.g. for listening on traversal changes. 136 * @return 137 */ 138 public final boolean canTraverse() { 139 return algorithm != null; 140 } 141 142 /** 143 * Gets the appropriate bounds for the given node, transformed into 144 * the scene's or the specified node's coordinates. 145 * @return bounds of node in {@code forParent} coordinates or scene coordinates if {@code forParent} is null 146 */ 147 private Bounds getLayoutBounds(Node n, Parent forParent) { 148 final Bounds bounds; 149 if (n != null) { 150 if (forParent == null) { 151 bounds = n.localToScene(n.getLayoutBounds()); 152 } else { 153 bounds = forParent.sceneToLocal(n.localToScene(n.getLayoutBounds())); 154 } 155 } else { 156 bounds = initialBounds; 157 } 158 return bounds; 159 } 160 161 // This is the engine context passed algorithm on select calls 162 private final class EngineContext extends BaseEngineContext { 163 @Override 164 public Parent getRoot() { 165 return TraversalEngine.this.getRoot(); 166 } 167 } 168 169 // This is the engine context passed to algorithm on select callbacks from other contexts. 170 // It can change the root to the node defined in "selectFirstInParent", "selectLastInParent" or 171 // "selectInSubtree" methods 172 private final class TempEngineContext extends BaseEngineContext { 173 private Parent root; 174 175 @Override 176 public Parent getRoot() { 177 return root; 178 } 179 180 public void setRoot(Parent root) { 181 this.root = root; 182 } 183 } 184 185 /** 186 * The base class for all engine contexts 187 */ 188 private abstract class BaseEngineContext implements TraversalContext { 189 190 /** 191 * Returns all traversable nodes in the context's (engine's) root 192 */ 193 @Override 194 public List<Node> getAllTargetNodes() { 195 final List<Node> targetNodes = new ArrayList<>(); 196 addFocusableChildrenToList(targetNodes, getRoot()); 197 return targetNodes; 198 } 199 200 @Override 201 public Bounds getSceneLayoutBounds(Node n) { 202 return getLayoutBounds(n, null); 203 } 204 205 private void addFocusableChildrenToList(List<Node> list, Parent parent) { 206 List<Node> parentsNodes = parent.getChildrenUnmodifiable(); 207 for (Node n : parentsNodes) { 208 if (n.isFocusTraversable() && !n.isFocused() && NodeHelper.isTreeVisible(n) && !n.isDisabled()) { 209 list.add(n); 210 } 211 if (n instanceof Parent) { 212 addFocusableChildrenToList(list, (Parent)n); 213 } 214 } 215 } 216 217 // All of the methods below are callbacks from traversal context to the default algorithm. 218 // They can be used to obtain "default" result for the specified subtree. 219 // This is useful when there is some algorithm that overrides behavior for a Parent but parent's children 220 // should be again traversed by default algorithm. 221 @Override 222 public Node selectFirstInParent(Parent parent) { 223 tempEngineContext.setRoot(parent); 224 return DEFAULT_ALGORITHM.selectFirst(tempEngineContext); 225 } 226 227 @Override 228 public Node selectLastInParent(Parent parent) { 229 tempEngineContext.setRoot(parent); 230 return DEFAULT_ALGORITHM.selectLast(tempEngineContext); 231 } 232 233 @Override 234 public Node selectInSubtree(Parent subTreeRoot, Node from, Direction dir) { 235 tempEngineContext.setRoot(subTreeRoot); 236 return DEFAULT_ALGORITHM.select(from, dir, tempEngineContext); 237 } 238 } 239 }