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 }