1 /*
   2  * Copyright (c) 2011, 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.scene.NodeHelper;
  29 import com.sun.javafx.scene.ParentHelper;
  30 import javafx.collections.ObservableList;
  31 import javafx.scene.Node;
  32 import javafx.scene.Parent;
  33 
  34 import java.util.List;
  35 
  36 final class TabOrderHelper {
  37     private static Node findPreviousFocusableInList(List<Node> nodeList, int startIndex) {
  38         for (int i = startIndex ; i >= 0 ; i--) {
  39             Node prevNode = nodeList.get(i);
  40             // ParentTraverEngine can override traversability, so we need to check it first
  41             if (isDisabledOrInvisible(prevNode)) continue;
  42             final ParentTraversalEngine traversalEngine = prevNode instanceof Parent
  43                     ? ParentHelper.getTraversalEngine((Parent) prevNode) : null;
  44             if (prevNode instanceof Parent) {
  45                 if (traversalEngine != null && traversalEngine.canTraverse()) {
  46                     Node selected = traversalEngine.selectLast();
  47                     if (selected != null) {
  48                         return selected;
  49                     }
  50                 } else {
  51                     List<Node> prevNodesList = ((Parent) prevNode).getChildrenUnmodifiable();
  52                     if (prevNodesList.size() > 0) {
  53                         Node newNode = findPreviousFocusableInList(prevNodesList, prevNodesList.size() - 1);
  54                         if (newNode != null) {
  55                             return newNode;
  56                         }
  57                     }
  58                 }
  59             }
  60             if (traversalEngine != null
  61                     ? traversalEngine.isParentTraversable()
  62                     : prevNode.isFocusTraversable()) {
  63                 return prevNode;
  64             }
  65         }
  66         return null;
  67     }
  68 
  69     private static boolean isDisabledOrInvisible(Node prevNode) {
  70         return prevNode.isDisabled() || !NodeHelper.isTreeVisible(prevNode);
  71     }
  72 
  73     public static Node findPreviousFocusablePeer(Node node, Parent root) {
  74         Node startNode = node;
  75         Node newNode = null;
  76         List<Node> parentNodes = findPeers(startNode);
  77 
  78         if (parentNodes == null) {
  79             // We are at top level, so select the last focusable node
  80             ObservableList<Node> rootChildren = ((Parent) node).getChildrenUnmodifiable();
  81             return findPreviousFocusableInList(rootChildren, rootChildren.size() - 1);
  82         }
  83 
  84         int ourIndex = parentNodes.indexOf(startNode);
  85 
  86         // Start with the siblings "to the left"
  87         newNode = findPreviousFocusableInList(parentNodes, ourIndex - 1);
  88 
  89         /*
  90         ** we've reached the end of the peer nodes, and none have been selected,
  91         ** time to look at our parents peers.....
  92         */
  93         while (newNode == null && startNode.getParent() != root) {
  94             List<Node> peerNodes;
  95             int parentIndex;
  96 
  97             Parent parent = startNode.getParent();
  98             if (parent != null) {
  99                 // If the parent itself is traversable, select it
 100                 final ParentTraversalEngine parentEngine
 101                         = ParentHelper.getTraversalEngine(parent);
 102                 if (parentEngine != null ? parentEngine.isParentTraversable() : parent.isFocusTraversable()) {
 103                     newNode = parent;
 104                 } else {
 105                     peerNodes = findPeers(parent);
 106                     if (peerNodes != null) {
 107                         parentIndex = peerNodes.indexOf(parent);
 108                         newNode = findPreviousFocusableInList(peerNodes, parentIndex - 1);
 109                     }
 110                 }
 111             }
 112             startNode = parent;
 113         }
 114 
 115         return newNode;
 116     }
 117 
 118     private static List<Node> findPeers(Node node) {
 119         List<Node> parentNodes = null;
 120         Parent parent = node.getParent();
 121         /*
 122         ** check that we haven't hit the top-level
 123         */
 124         if (parent != null) {
 125             parentNodes = parent.getChildrenUnmodifiable();
 126         }
 127         return parentNodes;
 128     }
 129 
 130     private static Node findNextFocusableInList(List<Node> nodeList, int startIndex) {
 131         for (int i = startIndex ; i < nodeList.size() ; i++) {
 132             Node nextNode = nodeList.get(i);
 133             if (isDisabledOrInvisible(nextNode)) continue;
 134             final ParentTraversalEngine traversalEngine = nextNode instanceof Parent
 135                     ? ParentHelper.getTraversalEngine((Parent) nextNode) : null;
 136             // ParentTraverEngine can override traversability, so we need to check it first
 137             if (traversalEngine != null
 138                     ? traversalEngine.isParentTraversable()
 139                     : nextNode.isFocusTraversable()) {
 140                 return nextNode;
 141             }
 142             else if (nextNode instanceof Parent) {
 143                 if (traversalEngine!= null && traversalEngine.canTraverse()) {
 144                     Node selected = traversalEngine.selectFirst();
 145                     if (selected != null) {
 146                         return selected;
 147                     } else {
 148                         // If the Parent has it's own engine, but no selection can be done, skip it
 149                         continue;
 150                     }
 151                 }
 152                 List<Node> nextNodesList = ((Parent)nextNode).getChildrenUnmodifiable();
 153                 if (nextNodesList.size() > 0) {
 154                     Node newNode = findNextFocusableInList(nextNodesList, 0);
 155                     if (newNode != null) {
 156                         return newNode;
 157                     }
 158                 }
 159             }
 160         }
 161         return null;
 162     }
 163 
 164     public static Node findNextFocusablePeer(Node node, Parent root, boolean traverseIntoCurrent) {
 165         Node startNode = node;
 166         Node newNode = null;
 167 
 168         // First, try to find next peer among the node children
 169         if (traverseIntoCurrent && node instanceof Parent) {
 170             newNode = findNextFocusableInList(((Parent)node).getChildrenUnmodifiable(), 0);
 171         }
 172 
 173         // Next step is to select the siblings "to the right"
 174         if (newNode == null) {
 175             List<Node> parentNodes = findPeers(startNode);
 176             if (parentNodes == null) {
 177                 // We got a top level Node that has no focusable children (we know that from the first step above), so
 178                 // there's nothing to do.
 179                 return null;
 180             }
 181             int ourIndex = parentNodes.indexOf(startNode);
 182             newNode = findNextFocusableInList(parentNodes, ourIndex + 1);
 183         }
 184 
 185         /*
 186         ** we've reached the end of the peer nodes, and none have been selected,
 187         ** time to look at our parents peers.....
 188         */
 189         while (newNode == null && startNode.getParent() != root) {
 190             List<Node> peerNodes;
 191             int parentIndex;
 192 
 193             Parent parent = startNode.getParent();
 194             if (parent != null) {
 195                 peerNodes = findPeers(parent);
 196                 if (peerNodes != null) {
 197                     parentIndex = peerNodes.indexOf(parent);
 198                     newNode = findNextFocusableInList(peerNodes, parentIndex + 1);
 199                 }
 200             }
 201             startNode = parent;
 202         }
 203 
 204         return newNode;
 205     }
 206 
 207     public static Node getFirstTargetNode(Parent p) {
 208         if (p == null || isDisabledOrInvisible(p)) return null;
 209         final ParentTraversalEngine traversalEngine
 210                 = ParentHelper.getTraversalEngine(p);
 211         if (traversalEngine!= null && traversalEngine.canTraverse()) {
 212             Node selected = traversalEngine.selectFirst();
 213             if (selected != null) {
 214                 return selected;
 215             }
 216         }
 217         List<Node> parentsNodes = p.getChildrenUnmodifiable();
 218         for (Node n : parentsNodes) {
 219             if (isDisabledOrInvisible(n)) continue;
 220             final ParentTraversalEngine parentEngine = n instanceof Parent
 221                     ? ParentHelper.getTraversalEngine((Parent)n) : null;
 222             if (parentEngine != null ? parentEngine.isParentTraversable() : n.isFocusTraversable()) {
 223                 return n;
 224             }
 225             if (n instanceof Parent) {
 226                 Node result = getFirstTargetNode((Parent)n);
 227                 if (result != null) return result;
 228             }
 229         }
 230         return null;
 231     }
 232 
 233     public static Node getLastTargetNode(Parent p) {
 234         if (p == null || isDisabledOrInvisible(p)) return null;
 235         final ParentTraversalEngine traversalEngine
 236                 = ParentHelper.getTraversalEngine(p);
 237         if (traversalEngine!= null && traversalEngine.canTraverse()) {
 238             Node selected = traversalEngine.selectLast();
 239             if (selected != null) {
 240                 return selected;
 241             }
 242         }
 243         List<Node> parentsNodes = p.getChildrenUnmodifiable();
 244         for (int i = parentsNodes.size() - 1; i >= 0; --i) {
 245             Node n = parentsNodes.get(i);
 246             if (isDisabledOrInvisible(n)) continue;
 247 
 248             if (n instanceof Parent) {
 249                 Node result = getLastTargetNode((Parent) n);
 250                 if (result != null) return result;
 251             }
 252             final ParentTraversalEngine parentEngine = n instanceof Parent
 253                     ? ParentHelper.getTraversalEngine((Parent) n) : null;
 254             if (parentEngine != null ? parentEngine.isParentTraversable() : n.isFocusTraversable()) {
 255                 return n;
 256             }
 257         }
 258         return null;
 259     }
 260 }