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 }