1 /*
   2  * Copyright (c) 2000, 2003, 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 package java.awt;
  26 
  27 import java.util.logging.*;
  28 
  29 /**
  30  * A FocusTraversalPolicy that determines traversal order based on the order
  31  * of child Components in a Container. From a particular focus cycle root, the
  32  * policy makes a pre-order traversal of the Component hierarchy, and traverses
  33  * a Container's children according to the ordering of the array returned by
  34  * <code>Container.getComponents()</code>. Portions of the hierarchy that are
  35  * not visible and displayable will not be searched.
  36  * <p>
  37  * By default, ContainerOrderFocusTraversalPolicy implicitly transfers focus
  38  * down-cycle. That is, during normal forward focus traversal, the Component
  39  * traversed after a focus cycle root will be the focus-cycle-root's default
  40  * Component to focus. This behavior can be disabled using the
  41  * <code>setImplicitDownCycleTraversal</code> method.
  42  * <p>
  43  * By default, methods of this class with return a Component only if it is
  44  * visible, displayable, enabled, and focusable. Subclasses can modify this
  45  * behavior by overriding the <code>accept</code> method.
  46  * <p>
  47  * This policy takes into account <a
  48  * href="doc-files/FocusSpec.html#FocusTraversalPolicyProviders">focus traversal
  49  * policy providers</a>.  When searching for first/last/next/previous Component,
  50  * if a focus traversal policy provider is encountered, its focus traversal
  51  * policy is used to perform the search operation.
  52  *
  53  * @author David Mendenhall
  54  *
  55  * @see Container#getComponents
  56  * @since 1.4
  57  */
  58 public class ContainerOrderFocusTraversalPolicy extends FocusTraversalPolicy
  59     implements java.io.Serializable
  60 {
  61     private static final MutableBoolean found = new MutableBoolean();
  62 
  63     private static final Logger log = Logger.getLogger("java.awt.ContainerOrderFocusTraversalPolicy");
  64 
  65     /*
  66      * JDK 1.4 serialVersionUID
  67      */
  68     private static final long serialVersionUID = 486933713763926351L;
  69 
  70     private boolean implicitDownCycleTraversal = true;
  71 
  72     /**
  73      * Returns the Component that should receive the focus after aComponent.
  74      * aContainer must be a focus cycle root of aComponent or a focus traversal policy provider.
  75      * <p>
  76      * By default, ContainerOrderFocusTraversalPolicy implicitly transfers
  77      * focus down-cycle. That is, during normal forward focus traversal, the
  78      * Component traversed after a focus cycle root will be the focus-cycle-
  79      * root's default Component to focus. This behavior can be disabled using
  80      * the <code>setImplicitDownCycleTraversal</code> method.
  81      * <p>
  82      * If aContainer is <a href="doc-files/FocusSpec.html#FocusTraversalPolicyProviders">focus
  83      * traversal policy provider</a>, the focus is always transferred down-cycle.
  84      *
  85      * @param aContainer a focus cycle root of aComponent or a focus traversal policy provider
  86      * @param aComponent a (possibly indirect) child of aContainer, or
  87      *        aContainer itself
  88      * @return the Component that should receive the focus after aComponent, or
  89      *         null if no suitable Component can be found
  90      * @throws IllegalArgumentException if aContainer is not a focus cycle
  91      *         root of aComponent or focus traversal policy provider, or if either aContainer or
  92      *         aComponent is null
  93      */
  94     public Component getComponentAfter(Container aContainer, Component aComponent) {
  95         if (log.isLoggable(Level.FINE)) {
  96             log.fine("Looking for next component in " + aContainer  + " for " + aComponent);
  97         }
  98         if (aContainer == null || aComponent == null) {
  99             throw new IllegalArgumentException("aContainer and aComponent cannot be null");
 100         }
 101         if (!aContainer.isFocusTraversalPolicyProvider() && !aContainer.isFocusCycleRoot()) {
 102             throw new IllegalArgumentException("aContainer should be focus cycle root or focus traversal policy provider");
 103         } else if (aContainer.isFocusCycleRoot() && !aComponent.isFocusCycleRoot(aContainer)) {
 104             throw new IllegalArgumentException("aContainer is not a focus cycle root of aComponent");
 105         }
 106 
 107         synchronized(aContainer.getTreeLock()) {
 108             found.value = false;
 109             Component retval = getComponentAfter(aContainer, aComponent,
 110                                                  found);
 111             if (retval != null) {
 112                 if (log.isLoggable(Level.FINE)) {
 113                     log.fine("After component is " + retval);
 114                 }
 115                 return retval;
 116             } else if (found.value) {
 117                 if (log.isLoggable(Level.FINE)) {
 118                     log.fine("Didn't find next component in " + aContainer + " - falling back to the first ");
 119                 }
 120                 return getFirstComponent(aContainer);
 121             } else {
 122                 if (log.isLoggable(Level.FINE)) {
 123                     log.fine("After component is null");
 124                 }
 125                 return null;
 126             }
 127         }
 128     }
 129 
 130     private Component getComponentAfter(Container aContainer,
 131                                         Component aComponent,
 132                                         MutableBoolean found) {
 133         if (!(aContainer.isVisible() && aContainer.isDisplayable())) {
 134             return null;
 135         }
 136 
 137         if (found.value) {
 138             if (accept(aContainer)) {
 139                 return aContainer;
 140             }
 141         } else if (aContainer == aComponent) {
 142             found.value = true;
 143         }
 144 
 145         for (int i = 0; i < aContainer.getComponentCount(); i++) {
 146             Component comp = aContainer.getComponent(i);
 147             if ((comp instanceof Container) &&
 148                 !((Container)comp).isFocusCycleRoot()) {
 149                 Component retval = null;
 150                 if (((Container)comp).isFocusTraversalPolicyProvider()) {
 151                     if (log.isLoggable(Level.FINE)) {
 152                         log.fine("Entering FTP " + comp);
 153                     }
 154                     Container cont = (Container) comp;
 155                     FocusTraversalPolicy policy = cont.getFocusTraversalPolicy();
 156                     if (log.isLoggable(Level.FINE)) {
 157                         log.fine("FTP contains " + aComponent + ": " + cont.isAncestorOf(aComponent));
 158                     }
 159                     if (found.value) {
 160                         retval = policy.getDefaultComponent(cont);
 161                         if (log.isLoggable(Level.FINE)) {
 162                             log.fine("Used FTP for getting default component: " + retval);
 163                         }
 164                     } else {
 165                         found.value = cont.isAncestorOf(aComponent);
 166                         if (found.value)  {
 167                             if (aComponent == policy.getLastComponent(cont)) {
 168                             // Reached last component, going to wrap - should switch to next provider
 169                             retval = null;
 170                             } else {
 171                                 retval = policy.getComponentAfter(cont, aComponent);
 172                                 if (log.isLoggable(Level.FINE)) {
 173                                     log.fine("FTP found next for the component : " + retval);
 174                                 }
 175                             }
 176                         }
 177                     }
 178                 } else {
 179                     retval = getComponentAfter((Container)comp,
 180                                                      aComponent,
 181                                                      found);
 182                 }
 183                 if (retval != null) {
 184                     return retval;
 185                 }
 186             } else if (found.value) {
 187                 if (accept(comp)) {
 188                     return comp;
 189                 }
 190             } else if (comp == aComponent) {
 191                 found.value = true;
 192             }
 193 
 194             if (found.value &&
 195                 getImplicitDownCycleTraversal() &&
 196                 (comp instanceof Container) &&
 197                 ((Container)comp).isFocusCycleRoot())
 198             {
 199                 Container cont = (Container)comp;
 200                 Component retval = cont.getFocusTraversalPolicy().
 201                     getDefaultComponent(cont);
 202                 if (retval != null) {
 203                     return retval;
 204                 }
 205             }
 206         }
 207 
 208         return null;
 209     }
 210 
 211     /**
 212      * Returns the Component that should receive the focus before aComponent.
 213      * aContainer must be a focus cycle root of aComponent or a <a
 214      * href="doc-files/FocusSpec.html#FocusTraversalPolicyProviders">focus traversal policy
 215      * provider</a>.
 216      *
 217      * @param aContainer a focus cycle root of aComponent or focus traversal policy provider
 218      * @param aComponent a (possibly indirect) child of aContainer, or
 219      *        aContainer itself
 220      * @return the Component that should receive the focus before aComponent,
 221      *         or null if no suitable Component can be found
 222      * @throws IllegalArgumentException if aContainer is not a focus cycle
 223      *         root of aComponent or focus traversal policy provider, or if either aContainer or
 224      *         aComponent is null
 225      */
 226     public Component getComponentBefore(Container aContainer,
 227                                         Component aComponent) {
 228         if (aContainer == null || aComponent == null) {
 229             throw new IllegalArgumentException("aContainer and aComponent cannot be null");
 230         }
 231         if (!aContainer.isFocusTraversalPolicyProvider() && !aContainer.isFocusCycleRoot()) {
 232             throw new IllegalArgumentException("aContainer should be focus cycle root or focus traversal policy provider");
 233         } else if (aContainer.isFocusCycleRoot() && !aComponent.isFocusCycleRoot(aContainer)) {
 234             throw new IllegalArgumentException("aContainer is not a focus cycle root of aComponent");
 235         }
 236         synchronized(aContainer.getTreeLock()) {
 237             found.value = false;
 238             Component retval = getComponentBefore(aContainer, aComponent,
 239                                                   found);
 240             if (retval != null) {
 241                 if (log.isLoggable(Level.FINE)) {
 242                     log.fine("Before component is " + retval);
 243                 }
 244                 return retval;
 245             } else if (found.value) {
 246                 if (log.isLoggable(Level.FINE)) {
 247                     log.fine("Didn't find before component in " + aContainer + " - falling back to the first ");
 248                 }
 249                 return getLastComponent(aContainer);
 250             } else {
 251                 if (log.isLoggable(Level.FINE)) {
 252                     log.fine("Before component is null");
 253                 }
 254                 return null;
 255             }
 256         }
 257     }
 258 
 259     private Component getComponentBefore(Container aContainer,
 260                                          Component aComponent,
 261                                          MutableBoolean found) {
 262         if (!(aContainer.isVisible() && aContainer.isDisplayable())) {
 263             return null;
 264         }
 265 
 266         for (int i = aContainer.getComponentCount() - 1; i >= 0; i--) {
 267             Component comp = aContainer.getComponent(i);
 268             if (comp == aComponent) {
 269                 found.value = true;
 270             } else if ((comp instanceof Container) &&
 271                 !((Container)comp).isFocusCycleRoot()) {
 272                 Component retval = null;
 273                 if (((Container)comp).isFocusTraversalPolicyProvider()) {
 274                     if (log.isLoggable(Level.FINE)) {
 275                         log.fine("Entering FTP " + comp);
 276                     }
 277                     Container cont = (Container) comp;
 278                     FocusTraversalPolicy policy = cont.getFocusTraversalPolicy();
 279                     if (log.isLoggable(Level.FINE)) {
 280                         log.fine("FTP contains " + aComponent + ": " + cont.isAncestorOf(aComponent));
 281                     }
 282                     if (found.value) {
 283                         retval = policy.getLastComponent(cont);
 284                         if (log.isLoggable(Level.FINE)) {
 285                             log.fine("Used FTP for getting last component: " + retval);
 286                         }
 287                     } else {
 288                         found.value = cont.isAncestorOf(aComponent);
 289                         if (found.value) {
 290                             if (aComponent == policy.getFirstComponent(cont)) {
 291                                 retval = null;
 292                             } else {
 293                                 retval = policy.getComponentBefore(cont, aComponent);
 294                                 if (log.isLoggable(Level.FINE)) {
 295                                     log.fine("FTP found previous for the component : " + retval);
 296                                 }
 297                             }
 298                         }
 299                     }
 300                 } else {
 301                     retval = getComponentBefore((Container)comp,
 302                                                       aComponent,
 303                                                       found);
 304                 }
 305                 if (retval != null) {
 306                     return retval;
 307                 }
 308             } else if (found.value) {
 309                 if (accept(comp)) {
 310                     return comp;
 311                 }
 312             }
 313         }
 314 
 315         if (found.value) {
 316             if (accept(aContainer)) {
 317                 return aContainer;
 318             }
 319         } else if (aContainer == aComponent) {
 320             found.value = true;
 321         }
 322 
 323         return null;
 324     }
 325 
 326     /**
 327      * Returns the first Component in the traversal cycle. This method is used
 328      * to determine the next Component to focus when traversal wraps in the
 329      * forward direction.
 330      *
 331      * @param aContainer the focus cycle root or focus traversal policy provider whose first
 332      *        Component is to be returned
 333      * @return the first Component in the traversal cycle of aContainer,
 334      *         or null if no suitable Component can be found
 335      * @throws IllegalArgumentException if aContainer is null
 336      */
 337     public Component getFirstComponent(Container aContainer) {
 338         if (aContainer == null) {
 339             throw new IllegalArgumentException("aContainer cannot be null");
 340         }
 341 
 342         synchronized(aContainer.getTreeLock()) {
 343             if (!(aContainer.isVisible() &&
 344                   aContainer.isDisplayable()))
 345             {
 346                 return null;
 347             }
 348 
 349             if (accept(aContainer)) {
 350                 return aContainer;
 351             }
 352 
 353             for (int i = 0; i < aContainer.getComponentCount(); i++) {
 354                 Component comp = aContainer.getComponent(i);
 355                 if (comp instanceof Container &&
 356                     !((Container)comp).isFocusCycleRoot())
 357                 {
 358                     Component retval = null;
 359                     Container cont = (Container)comp;
 360                     if (cont.isFocusTraversalPolicyProvider()) {
 361                         FocusTraversalPolicy policy = cont.getFocusTraversalPolicy();
 362                         retval = policy.getDefaultComponent(cont);
 363                     } else {
 364                         retval = getFirstComponent((Container)comp);
 365                     }
 366                     if (retval != null) {
 367                         return retval;
 368                     }
 369                 } else if (accept(comp)) {
 370                     return comp;
 371                 }
 372             }
 373         }
 374 
 375         return null;
 376     }
 377 
 378     /**
 379      * Returns the last Component in the traversal cycle. This method is used
 380      * to determine the next Component to focus when traversal wraps in the
 381      * reverse direction.
 382      *
 383      * @param aContainer the focus cycle root or focus traversal policy provider whose last
 384      *        Component is to be returned
 385      * @return the last Component in the traversal cycle of aContainer,
 386      *         or null if no suitable Component can be found
 387      * @throws IllegalArgumentException if aContainer is null
 388      */
 389     public Component getLastComponent(Container aContainer) {
 390         if (aContainer == null) {
 391             throw new IllegalArgumentException("aContainer cannot be null");
 392         }
 393         if (log.isLoggable(Level.FINE)) {
 394             log.fine("Looking for the last component in " + aContainer);
 395         }
 396 
 397         synchronized(aContainer.getTreeLock()) {
 398             if (!(aContainer.isVisible() &&
 399                   aContainer.isDisplayable()))
 400             {
 401                 return null;
 402             }
 403 
 404             for (int i = aContainer.getComponentCount() - 1; i >= 0; i--) {
 405                 Component comp = aContainer.getComponent(i);
 406                 if (comp instanceof Container &&
 407                     !((Container)comp).isFocusCycleRoot())
 408                 {
 409                     Component retval = null;
 410                     Container cont = (Container)comp;
 411                     if (cont.isFocusTraversalPolicyProvider()) {
 412                         if (log.isLoggable(Level.FINE)) {
 413                             log.fine("\tEntering FTP " + cont);
 414                         }
 415                         FocusTraversalPolicy policy = cont.getFocusTraversalPolicy();
 416                         retval = policy.getLastComponent(cont);
 417                     } else {
 418                         if (log.isLoggable(Level.FINE)) {
 419                             log.fine("\tEntering sub-container");
 420                         }
 421                         retval = getLastComponent((Container)comp);
 422                     }
 423                     if (retval != null) {
 424                         if (log.isLoggable(Level.FINE)) {
 425                             log.fine("\tFound last component : " + retval);
 426                         }
 427                         return retval;
 428                     }
 429                 } else if (accept(comp)) {
 430                     return comp;
 431                 }
 432             }
 433 
 434             if (accept(aContainer)) {
 435                 return aContainer;
 436             }
 437         }
 438 
 439         return null;
 440     }
 441 
 442     /**
 443      * Returns the default Component to focus. This Component will be the first
 444      * to receive focus when traversing down into a new focus traversal cycle
 445      * rooted at aContainer. The default implementation of this method
 446      * returns the same Component as <code>getFirstComponent</code>.
 447      *
 448      * @param aContainer the focus cycle root or focus traversal policy provider whose default
 449      *        Component is to be returned
 450      * @return the default Component in the traversal cycle of aContainer,
 451      *         or null if no suitable Component can be found
 452      * @see #getFirstComponent
 453      * @throws IllegalArgumentException if aContainer is null
 454      */
 455     public Component getDefaultComponent(Container aContainer) {
 456         return getFirstComponent(aContainer);
 457     }
 458 
 459     /**
 460      * Sets whether this ContainerOrderFocusTraversalPolicy transfers focus
 461      * down-cycle implicitly. If <code>true</code>, during normal forward focus
 462      * traversal, the Component traversed after a focus cycle root will be the
 463      * focus-cycle-root's default Component to focus. If <code>false</code>,
 464      * the next Component in the focus traversal cycle rooted at the specified
 465      * focus cycle root will be traversed instead. The default value for this
 466      * property is <code>true</code>.
 467      *
 468      * @param implicitDownCycleTraversal whether this
 469      *        ContainerOrderFocusTraversalPolicy transfers focus down-cycle
 470      *        implicitly
 471      * @see #getImplicitDownCycleTraversal
 472      * @see #getFirstComponent
 473      */
 474     public void setImplicitDownCycleTraversal(boolean
 475                                               implicitDownCycleTraversal) {
 476         this.implicitDownCycleTraversal = implicitDownCycleTraversal;
 477     }
 478 
 479     /**
 480      * Returns whether this ContainerOrderFocusTraversalPolicy transfers focus
 481      * down-cycle implicitly. If <code>true</code>, during normal forward focus
 482      * traversal, the Component traversed after a focus cycle root will be the
 483      * focus-cycle-root's default Component to focus. If <code>false</code>,
 484      * the next Component in the focus traversal cycle rooted at the specified
 485      * focus cycle root will be traversed instead.
 486      *
 487      * @return whether this ContainerOrderFocusTraversalPolicy transfers focus
 488      *         down-cycle implicitly
 489      * @see #setImplicitDownCycleTraversal
 490      * @see #getFirstComponent
 491      */
 492     public boolean getImplicitDownCycleTraversal() {
 493         return implicitDownCycleTraversal;
 494     }
 495 
 496     /**
 497      * Determines whether a Component is an acceptable choice as the new
 498      * focus owner. By default, this method will accept a Component if and
 499      * only if it is visible, displayable, enabled, and focusable.
 500      *
 501      * @param aComponent the Component whose fitness as a focus owner is to
 502      *        be tested
 503      * @return <code>true</code> if aComponent is visible, displayable,
 504      *         enabled, and focusable; <code>false</code> otherwise
 505      */
 506     protected boolean accept(Component aComponent) {
 507         if (!aComponent.canBeFocusOwner()) {
 508             return false;
 509         }
 510 
 511         // Verify that the Component is recursively enabled. Disabling a
 512         // heavyweight Container disables its children, whereas disabling
 513         // a lightweight Container does not.
 514         if (!(aComponent instanceof Window)) {
 515             for (Container enableTest = aComponent.getParent();
 516                  enableTest != null;
 517                  enableTest = enableTest.getParent())
 518             {
 519                 if (!(enableTest.isEnabled() || enableTest.isLightweight())) {
 520                     return false;
 521                 }
 522                 if (enableTest instanceof Window) {
 523                     break;
 524                 }
 525             }
 526         }
 527 
 528         return true;
 529     }
 530 
 531 }
 532 
 533 
 534 class MutableBoolean {
 535     boolean value = false;
 536 }