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 javafx.geometry.BoundingBox; 30 import javafx.geometry.Bounds; 31 import javafx.scene.Node; 32 import javafx.scene.Parent; 33 34 import java.util.ArrayList; 35 import java.util.List; 36 37 /** 38 * This is abstract class for a traversal engine. There are 2 types : {@link com.sun.javafx.scene.traversal.ParentTraversalEngine} 39 * to be used in {@link Parent#setTraversalEngine(ParentTraversalEngine)} to override default behavior 40 * and {@link com.sun.javafx.scene.traversal.TopMostTraversalEngine} that is the default traversal engine for scene and subscene. 41 * 42 * Every engine is basically a wrapper of an algorithm + some specific parent (or scene/subscene), which define engine's root. 43 */ 44 public abstract class TraversalEngine{ 45 46 /** 47 * This is the default algorithm for the running platform. It's the algorithm that's used in TopMostTraversalEngine 48 */ 49 static final Algorithm DEFAULT_ALGORITHM = PlatformImpl.isContextual2DNavigation() ? new Hueristic2D() : new ContainerTabOrder(); 50 51 private final TraversalContext context = new EngineContext(); // This is the context used in calls to this engine's algorithm 52 // This is a special context that's used when invoking select "callbacks" to default algorithm in other contexts 53 private final TempEngineContext tempEngineContext = new TempEngineContext(); 54 protected final Algorithm algorithm; 55 56 private final Bounds initialBounds = new BoundingBox(0, 0, 1, 1); 57 private final ArrayList<TraverseListener> listeners = new ArrayList<>(); 58 59 /** 60 * Creates engine with the specified algorithm 61 * @param algorithm 62 */ 63 protected TraversalEngine(Algorithm algorithm) { 64 this.algorithm = algorithm; 65 } 66 67 /** 68 * Creates engine with no algorithm. This makes all the select* calls invalid. 69 * @see #canTraverse() 70 */ 71 protected TraversalEngine() { 72 this.algorithm = null; 73 } 74 75 /** 76 * Add a listener to traversal engine. The listener is notified whenever focus is changed by traversal inside the associated scene or parent. 77 * This can be used with ParentTraversalEngine that has no algorithm to observe changes to the focus inside the parent. 78 * @param listener 79 */ 80 public final void addTraverseListener(TraverseListener listener) { 81 listeners.add(listener); 82 } 83 84 /** 85 * Fire notifications for listeners. This is called from the TopMostTraversalEngine 86 * @param newNode the node which has been focused 87 */ 88 final void notifyTraversedTo(Node newNode) { 89 for (TraverseListener l : listeners) { 90 l.onTraverse(newNode, getLayoutBounds(newNode, getRoot())); 91 } 92 } 93 94 /** 95 * Returns the node that is in the direction {@code dir} starting from the Node {@code from} using the engine's algorithm. 96 * Null means there is no Node in that direction 97 * @param from the node to start traversal from 98 * @param dir the direction of traversal 99 * @return the subsequent node in the specified direction or null if none 100 * @throws java.lang.NullPointerException if there is no algorithm 101 */ 102 public final Node select(Node from, Direction dir) { 103 return algorithm.select(from, dir, context); 104 } 105 106 /** 107 * Returns the first node in this engine's context (scene/parent) using the engine's algorithm. 108 * This can be null only if there are no traversable nodes 109 * @return The first node or null if none exists 110 * @throws java.lang.NullPointerException if there is no algorithm 111 */ 112 public final Node selectFirst() { 113 return algorithm.selectFirst(context); 114 } 115 116 /** 117 * Returns the last node in this engine's context (scene/parent) using the engine's algorithm. 118 * This can be null only if there are no traversable nodes 119 * @return The last node or null if none exists 120 * @throws java.lang.NullPointerException if there is no algorithm 121 */ 122 public final Node selectLast() { 123 return algorithm.selectLast(context); 124 } 125 126 /** 127 * The root of this engine's context. This is the node that is the root of the tree that is traversed by this engine. 128 * @return This engine's root 129 */ 130 protected abstract Parent getRoot(); 131 132 /** 133 * Returns true only if there's specified algorithm for this engine. Otherwise, this engine cannot be used for traversal. 134 * The engine might be still useful however, e.g. for listening on traversal changes. 135 * @return 136 */ 137 public final boolean canTraverse() { 138 return algorithm != null; 139 } 140 141 /** 142 * Gets the appropriate bounds for the given node, transformed into 143 * the scene's or the specified node's coordinates. 144 * @return bounds of node in {@code forParent} coordinates or scene coordinates if {@code forParent} is null 145 */ 146 private Bounds getLayoutBounds(Node n, Parent forParent) { 147 final Bounds bounds; 148 if (n != null) { 149 if (forParent == null) { 150 bounds = n.localToScene(n.getLayoutBounds()); 151 } else { 152 bounds = forParent.sceneToLocal(n.localToScene(n.getLayoutBounds())); 153 } 154 } else { 155 bounds = initialBounds; 156 } 157 return bounds; 158 } 159 160 // This is the engine context passed algorithm on select calls 161 private final class EngineContext extends BaseEngineContext { 162 @Override 163 public Parent getRoot() { 164 return TraversalEngine.this.getRoot(); 165 } 166 } 167 168 // This is the engine context passed to algorithm on select callbacks from other contexts. 169 // It can change the root to the node defined in "selectFirstInParent", "selectLastInParent" or 170 // "selectInSubtree" methods 171 private final class TempEngineContext extends BaseEngineContext { 172 private Parent root; 173 174 @Override 175 public Parent getRoot() { 176 return root; 177 } 178 179 public void setRoot(Parent root) { 180 this.root = root; 181 } 182 } 183 184 /** 185 * The base class for all engine contexts 186 */ 187 private abstract class BaseEngineContext implements TraversalContext { 188 189 /** 190 * Returns all traversable nodes in the context's (engine's) root 191 */ 192 @Override 193 public List<Node> getAllTargetNodes() { 194 final List<Node> targetNodes = new ArrayList<>(); 195 addFocusableChildrenToList(targetNodes, getRoot()); 196 return targetNodes; 197 } 198 199 @Override 200 public Bounds getSceneLayoutBounds(Node n) { 201 return getLayoutBounds(n, null); 202 } 203 204 private void addFocusableChildrenToList(List<Node> list, Parent parent) { 205 List<Node> parentsNodes = parent.getChildrenUnmodifiable(); 206 for (Node n : parentsNodes) { 207 if (n.isFocusTraversable() && !n.isFocused() && n.impl_isTreeVisible() && !n.isDisabled()) { 208 list.add(n); 209 } 210 if (n instanceof Parent) { 211 addFocusableChildrenToList(list, (Parent)n); 212 } 213 } 214 } 215 216 // All of the methods below are callbacks from traversal context to the default algorithm. 217 // They can be used to obtain "default" result for the specified subtree. 218 // This is useful when there is some algorithm that overrides behavior for a Parent but parent's children 219 // should be again traversed by default algorithm. 220 @Override 221 public Node selectFirstInParent(Parent parent) { 222 tempEngineContext.setRoot(parent); 223 return DEFAULT_ALGORITHM.selectFirst(tempEngineContext); 224 } 225 226 @Override 227 public Node selectLastInParent(Parent parent) { 228 tempEngineContext.setRoot(parent); 229 return DEFAULT_ALGORITHM.selectLast(tempEngineContext); 230 } 231 232 @Override 233 public Node selectInSubtree(Parent subTreeRoot, Node from, Direction dir) { 234 tempEngineContext.setRoot(subTreeRoot); 235 return DEFAULT_ALGORITHM.select(from, dir, tempEngineContext); 236 } 237 } 238 }