1 /* 2 * Copyright (c) 2000, 2019, 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.List; 28 import java.util.ArrayList; 29 import sun.util.logging.PlatformLogger; 30 31 import java.util.Enumeration; 32 import sun.awt.SunToolkit; 33 import javax.swing.AbstractButton; 34 import javax.swing.ButtonGroup; 35 import javax.swing.ButtonModel; 36 import javax.swing.JToggleButton; 37 38 39 /** 40 * A FocusTraversalPolicy that determines traversal order based on the order 41 * of child Components in a Container. From a particular focus cycle root, the 42 * policy makes a pre-order traversal of the Component hierarchy, and traverses 43 * a Container's children according to the ordering of the array returned by 44 * {@code Container.getComponents()}. Portions of the hierarchy that are 45 * not visible and displayable will not be searched. 46 * <p> 47 * By default, ContainerOrderFocusTraversalPolicy implicitly transfers focus 48 * down-cycle. That is, during normal forward focus traversal, the Component 49 * traversed after a focus cycle root will be the focus-cycle-root's default 50 * Component to focus. This behavior can be disabled using the 51 * {@code setImplicitDownCycleTraversal} method. 52 * <p> 53 * By default, methods of this class will return a Component only if it is 54 * visible, displayable, enabled, and focusable. Subclasses can modify this 55 * behavior by overriding the {@code accept} method. 56 * <p> 57 * This policy takes into account <a 58 * href="doc-files/FocusSpec.html#FocusTraversalPolicyProviders">focus traversal 59 * policy providers</a>. When searching for first/last/next/previous Component, 60 * if a focus traversal policy provider is encountered, its focus traversal 61 * policy is used to perform the search operation. 62 * 63 * @author David Mendenhall 64 * 65 * @see Container#getComponents 66 * @since 1.4 67 */ 68 public class ContainerOrderFocusTraversalPolicy extends FocusTraversalPolicy 69 implements java.io.Serializable 70 { 71 private static final PlatformLogger log = PlatformLogger.getLogger("java.awt.ContainerOrderFocusTraversalPolicy"); 72 73 private final int FORWARD_TRAVERSAL = 0; 74 private final int BACKWARD_TRAVERSAL = 1; 75 76 /* 77 * JDK 1.4 serialVersionUID 78 */ 79 private static final long serialVersionUID = 486933713763926351L; 80 81 private boolean implicitDownCycleTraversal = true; 82 83 /** 84 * Used by getComponentAfter and getComponentBefore for efficiency. In 85 * order to maintain compliance with the specification of 86 * FocusTraversalPolicy, if traversal wraps, we should invoke 87 * getFirstComponent or getLastComponent. These methods may be overriden in 88 * subclasses to behave in a non-generic way. However, in the generic case, 89 * these methods will simply return the first or last Components of the 90 * sorted list, respectively. Since getComponentAfter and 91 * getComponentBefore have already built the list before determining 92 * that they need to invoke getFirstComponent or getLastComponent, the 93 * list should be reused if possible. 94 */ 95 private transient Container cachedRoot; 96 private transient List<Component> cachedCycle; 97 98 /* 99 * We suppose to use getFocusTraversalCycle & getComponentIndex methods in order 100 * to divide the policy into two parts: 101 * 1) Making the focus traversal cycle. 102 * 2) Traversing the cycle. 103 * The 1st point assumes producing a list of components representing the focus 104 * traversal cycle. The two methods mentioned above should implement this logic. 105 * The 2nd point assumes implementing the common concepts of operating on the 106 * cycle: traversing back and forth, retrieving the initial/default/first/last 107 * component. These concepts are described in the AWT Focus Spec and they are 108 * applied to the FocusTraversalPolicy in general. 109 * Thus, a descendant of this policy may wish to not reimplement the logic of 110 * the 2nd point but just override the implementation of the 1st one. 111 * A striking example of such a descendant is the javax.swing.SortingFocusTraversalPolicy. 112 */ 113 /*protected*/ private List<Component> getFocusTraversalCycle(Container aContainer) { 114 List<Component> cycle = new ArrayList<Component>(); 115 enumerateCycle(aContainer, cycle); 116 return cycle; 117 } 118 /*protected*/ private int getComponentIndex(List<Component> cycle, Component aComponent) { 119 return cycle.indexOf(aComponent); 120 } 121 122 private void enumerateCycle(Container container, List<Component> cycle) { 123 if (!(container.isVisible() && container.isDisplayable())) { 124 return; 125 } 126 127 cycle.add(container); 128 129 Component[] components = container.getComponents(); 130 for (int i = 0; i < components.length; i++) { 131 Component comp = components[i]; 132 if (comp instanceof Container) { 133 Container cont = (Container)comp; 134 135 if (!cont.isFocusCycleRoot() && !cont.isFocusTraversalPolicyProvider()) { 136 enumerateCycle(cont, cycle); 137 continue; 138 } 139 } 140 cycle.add(comp); 141 } 142 } 143 144 private Container getTopmostProvider(Container focusCycleRoot, Component aComponent) { 145 Container aCont = aComponent.getParent(); 146 Container ftp = null; 147 while (aCont != focusCycleRoot && aCont != null) { 148 if (aCont.isFocusTraversalPolicyProvider()) { 149 ftp = aCont; 150 } 151 aCont = aCont.getParent(); 152 } 153 if (aCont == null) { 154 return null; 155 } 156 return ftp; 157 } 158 159 /* 160 * Checks if a new focus cycle takes place and returns a Component to traverse focus to. 161 * @param comp a possible focus cycle root or policy provider 162 * @param traversalDirection the direction of the traversal 163 * @return a Component to traverse focus to if {@code comp} is a root or provider 164 * and implicit down-cycle is set, otherwise {@code null} 165 */ 166 private Component getComponentDownCycle(Component comp, int traversalDirection) { 167 Component retComp = null; 168 169 if (comp instanceof Container) { 170 Container cont = (Container)comp; 171 172 if (cont.isFocusCycleRoot()) { 173 if (getImplicitDownCycleTraversal()) { 174 retComp = cont.getFocusTraversalPolicy().getDefaultComponent(cont); 175 176 if (retComp != null && log.isLoggable(PlatformLogger.Level.FINE)) { 177 log.fine("### Transferred focus down-cycle to " + retComp + 178 " in the focus cycle root " + cont); 179 } 180 } else { 181 return null; 182 } 183 } else if (cont.isFocusTraversalPolicyProvider()) { 184 retComp = (traversalDirection == FORWARD_TRAVERSAL ? 185 cont.getFocusTraversalPolicy().getDefaultComponent(cont) : 186 cont.getFocusTraversalPolicy().getLastComponent(cont)); 187 188 if (retComp != null && log.isLoggable(PlatformLogger.Level.FINE)) { 189 log.fine("### Transferred focus to " + retComp + " in the FTP provider " + cont); 190 } 191 } 192 } 193 return retComp; 194 } 195 196 /** 197 * Returns the Component that should receive the focus after aComponent. 198 * aContainer must be a focus cycle root of aComponent or a focus traversal policy provider. 199 * <p> 200 * By default, ContainerOrderFocusTraversalPolicy implicitly transfers 201 * focus down-cycle. That is, during normal forward focus traversal, the 202 * Component traversed after a focus cycle root will be the focus-cycle- 203 * root's default Component to focus. This behavior can be disabled using 204 * the {@code setImplicitDownCycleTraversal} method. 205 * <p> 206 * If aContainer is <a href="doc-files/FocusSpec.html#FocusTraversalPolicyProviders">focus 207 * traversal policy provider</a>, the focus is always transferred down-cycle. 208 * 209 * @param aContainer a focus cycle root of aComponent or a focus traversal policy provider 210 * @param aComponent a (possibly indirect) child of aContainer, or 211 * aContainer itself 212 * @return the Component that should receive the focus after aComponent, or 213 * null if no suitable Component can be found 214 * @throws IllegalArgumentException if aContainer is not a focus cycle 215 * root of aComponent or focus traversal policy provider, or if either aContainer or 216 * aComponent is null 217 */ 218 public Component getComponentAfter(Container aContainer, Component aComponent) { 219 if (log.isLoggable(PlatformLogger.Level.FINE)) { 220 log.fine("### Searching in " + aContainer + " for component after " + aComponent); 221 } 222 223 if (aContainer == null || aComponent == null) { 224 throw new IllegalArgumentException("aContainer and aComponent cannot be null"); 225 } 226 if (!aContainer.isFocusTraversalPolicyProvider() && !aContainer.isFocusCycleRoot()) { 227 throw new IllegalArgumentException("aContainer should be focus cycle root or focus traversal policy provider"); 228 229 } else if (aContainer.isFocusCycleRoot() && !aComponent.isFocusCycleRoot(aContainer)) { 230 throw new IllegalArgumentException("aContainer is not a focus cycle root of aComponent"); 231 } 232 233 synchronized(aContainer.getTreeLock()) { 234 235 if (!(aContainer.isVisible() && aContainer.isDisplayable())) { 236 return null; 237 } 238 239 // Before all the checks below we first see if it's an FTP provider or a focus cycle root. 240 // If it's the case just go down cycle (if it's set to "implicit"). 241 Component comp = getComponentDownCycle(aComponent, FORWARD_TRAVERSAL); 242 // Check if aComponent is focus-cycle-root's default Component, i.e. 243 // focus cycle root & focus-cycle-root's default Component is same. 244 if (comp != null && comp != aComponent) { 245 return comp; 246 } 247 248 // See if the component is inside of policy provider. 249 Container provider = getTopmostProvider(aContainer, aComponent); 250 if (provider != null) { 251 if (log.isLoggable(PlatformLogger.Level.FINE)) { 252 log.fine("### Asking FTP " + provider + " for component after " + aComponent); 253 } 254 255 // FTP knows how to find component after the given. We don't. 256 FocusTraversalPolicy policy = provider.getFocusTraversalPolicy(); 257 Component afterComp = policy.getComponentAfter(provider, aComponent); 258 259 // Null result means that we overstepped the limit of the FTP's cycle. 260 // In that case we must quit the cycle, otherwise return the component found. 261 if (afterComp != null) { 262 if (log.isLoggable(PlatformLogger.Level.FINE)) { 263 log.fine("### FTP returned " + afterComp); 264 } 265 return afterComp; 266 } 267 aComponent = provider; 268 } 269 270 List<Component> cycle = getFocusTraversalCycle(aContainer); 271 272 if (log.isLoggable(PlatformLogger.Level.FINE)) { 273 log.fine("### Cycle is " + cycle + ", component is " + aComponent); 274 } 275 276 int index = getComponentIndex(cycle, aComponent); 277 278 if (index < 0) { 279 if (log.isLoggable(PlatformLogger.Level.FINE)) { 280 log.fine("### Didn't find component " + aComponent + " in a cycle " + aContainer); 281 } 282 return getFirstComponent(aContainer); 283 } 284 285 for (index++; index < cycle.size(); index++) { 286 comp = cycle.get(index); 287 if (accept(comp)) { 288 return comp; 289 } else if ((comp = getComponentDownCycle(comp, FORWARD_TRAVERSAL)) != null) { 290 return comp; 291 } 292 } 293 294 if (aContainer.isFocusCycleRoot()) { 295 this.cachedRoot = aContainer; 296 this.cachedCycle = cycle; 297 298 comp = getFirstComponent(aContainer); 299 300 this.cachedRoot = null; 301 this.cachedCycle = null; 302 303 return comp; 304 } 305 } 306 return null; 307 } 308 309 /** 310 * Returns the Component that should receive the focus before aComponent. 311 * aContainer must be a focus cycle root of aComponent or a <a 312 * href="doc-files/FocusSpec.html#FocusTraversalPolicyProviders">focus traversal policy 313 * provider</a>. 314 * 315 * @param aContainer a focus cycle root of aComponent or focus traversal policy provider 316 * @param aComponent a (possibly indirect) child of aContainer, or 317 * aContainer itself 318 * @return the Component that should receive the focus before aComponent, 319 * or null if no suitable Component can be found 320 * @throws IllegalArgumentException if aContainer is not a focus cycle 321 * root of aComponent or focus traversal policy provider, or if either aContainer or 322 * aComponent is null 323 */ 324 public Component getComponentBefore(Container aContainer, Component aComponent) { 325 if (aContainer == null || aComponent == null) { 326 throw new IllegalArgumentException("aContainer and aComponent cannot be null"); 327 } 328 if (!aContainer.isFocusTraversalPolicyProvider() && !aContainer.isFocusCycleRoot()) { 329 throw new IllegalArgumentException("aContainer should be focus cycle root or focus traversal policy provider"); 330 331 } else if (aContainer.isFocusCycleRoot() && !aComponent.isFocusCycleRoot(aContainer)) { 332 throw new IllegalArgumentException("aContainer is not a focus cycle root of aComponent"); 333 } 334 335 synchronized(aContainer.getTreeLock()) { 336 337 if (!(aContainer.isVisible() && aContainer.isDisplayable())) { 338 return null; 339 } 340 341 // See if the component is inside of policy provider. 342 Container provider = getTopmostProvider(aContainer, aComponent); 343 if (provider != null) { 344 if (log.isLoggable(PlatformLogger.Level.FINE)) { 345 log.fine("### Asking FTP " + provider + " for component after " + aComponent); 346 } 347 348 // FTP knows how to find component after the given. We don't. 349 FocusTraversalPolicy policy = provider.getFocusTraversalPolicy(); 350 Component beforeComp = policy.getComponentBefore(provider, aComponent); 351 352 // Null result means that we overstepped the limit of the FTP's cycle. 353 // In that case we must quit the cycle, otherwise return the component found. 354 if (beforeComp != null) { 355 if (log.isLoggable(PlatformLogger.Level.FINE)) { 356 log.fine("### FTP returned " + beforeComp); 357 } 358 return beforeComp; 359 } 360 aComponent = provider; 361 362 // If the provider is traversable it's returned. 363 if (accept(aComponent)) { 364 return aComponent; 365 } 366 } 367 368 List<Component> cycle = getFocusTraversalCycle(aContainer); 369 370 if (log.isLoggable(PlatformLogger.Level.FINE)) { 371 log.fine("### Cycle is " + cycle + ", component is " + aComponent); 372 } 373 374 int index = getComponentIndex(cycle, aComponent); 375 376 if (index < 0) { 377 if (log.isLoggable(PlatformLogger.Level.FINE)) { 378 log.fine("### Didn't find component " + aComponent + " in a cycle " + aContainer); 379 } 380 return getLastComponent(aContainer); 381 } 382 383 Component comp = null; 384 Component tryComp = null; 385 386 for (index--; index>=0; index--) { 387 comp = cycle.get(index); 388 if (comp != aContainer && (tryComp = getComponentDownCycle(comp, BACKWARD_TRAVERSAL)) != null) { 389 return tryComp; 390 } else if (accept(comp)) { 391 return comp; 392 } 393 } 394 395 if (aContainer.isFocusCycleRoot()) { 396 this.cachedRoot = aContainer; 397 this.cachedCycle = cycle; 398 399 comp = getLastComponent(aContainer); 400 401 this.cachedRoot = null; 402 this.cachedCycle = null; 403 404 return comp; 405 } 406 } 407 return null; 408 } 409 410 /** 411 * Returns the first Component in the traversal cycle. This method is used 412 * to determine the next Component to focus when traversal wraps in the 413 * forward direction. 414 * 415 * @param aContainer the focus cycle root or focus traversal policy provider whose first 416 * Component is to be returned 417 * @return the first Component in the traversal cycle of aContainer, 418 * or null if no suitable Component can be found 419 * @throws IllegalArgumentException if aContainer is null 420 */ 421 public Component getFirstComponent(Container aContainer) { 422 List<Component> cycle; 423 424 if (log.isLoggable(PlatformLogger.Level.FINE)) { 425 log.fine("### Getting first component in " + aContainer); 426 } 427 if (aContainer == null) { 428 throw new IllegalArgumentException("aContainer cannot be null"); 429 430 } 431 432 synchronized(aContainer.getTreeLock()) { 433 434 if (!(aContainer.isVisible() && aContainer.isDisplayable())) { 435 return null; 436 } 437 438 if (this.cachedRoot == aContainer) { 439 cycle = this.cachedCycle; 440 } else { 441 cycle = getFocusTraversalCycle(aContainer); 442 } 443 444 if (cycle.size() == 0) { 445 if (log.isLoggable(PlatformLogger.Level.FINE)) { 446 log.fine("### Cycle is empty"); 447 } 448 return null; 449 } 450 if (log.isLoggable(PlatformLogger.Level.FINE)) { 451 log.fine("### Cycle is " + cycle); 452 } 453 454 for (Component comp : cycle) { 455 if (accept(comp)) { 456 return comp; 457 } else if (comp != aContainer && 458 (comp = getComponentDownCycle(comp, FORWARD_TRAVERSAL)) != null) 459 { 460 return comp; 461 } 462 } 463 } 464 return null; 465 } 466 467 /** 468 * Returns the last Component in the traversal cycle. This method is used 469 * to determine the next Component to focus when traversal wraps in the 470 * reverse direction. 471 * 472 * @param aContainer the focus cycle root or focus traversal policy provider whose last 473 * Component is to be returned 474 * @return the last Component in the traversal cycle of aContainer, 475 * or null if no suitable Component can be found 476 * @throws IllegalArgumentException if aContainer is null 477 */ 478 public Component getLastComponent(Container aContainer) { 479 List<Component> cycle; 480 if (log.isLoggable(PlatformLogger.Level.FINE)) { 481 log.fine("### Getting last component in " + aContainer); 482 } 483 484 if (aContainer == null) { 485 throw new IllegalArgumentException("aContainer cannot be null"); 486 } 487 488 synchronized(aContainer.getTreeLock()) { 489 490 if (!(aContainer.isVisible() && aContainer.isDisplayable())) { 491 return null; 492 } 493 494 if (this.cachedRoot == aContainer) { 495 cycle = this.cachedCycle; 496 } else { 497 cycle = getFocusTraversalCycle(aContainer); 498 } 499 500 if (cycle.size() == 0) { 501 if (log.isLoggable(PlatformLogger.Level.FINE)) { 502 log.fine("### Cycle is empty"); 503 } 504 return null; 505 } 506 if (log.isLoggable(PlatformLogger.Level.FINE)) { 507 log.fine("### Cycle is " + cycle); 508 } 509 510 for (int i= cycle.size() - 1; i >= 0; i--) { 511 Component comp = cycle.get(i); 512 if (accept(comp)) { 513 return comp; 514 } else if (comp instanceof Container && comp != aContainer) { 515 Container cont = (Container)comp; 516 if (cont.isFocusTraversalPolicyProvider()) { 517 Component retComp = cont.getFocusTraversalPolicy().getLastComponent(cont); 518 if (retComp != null) { 519 return retComp; 520 } 521 } 522 } 523 } 524 } 525 return null; 526 } 527 528 /** 529 * Returns the default Component to focus. This Component will be the first 530 * to receive focus when traversing down into a new focus traversal cycle 531 * rooted at aContainer. The default implementation of this method 532 * returns the same Component as {@code getFirstComponent}. 533 * 534 * @param aContainer the focus cycle root or focus traversal policy provider whose default 535 * Component is to be returned 536 * @return the default Component in the traversal cycle of aContainer, 537 * or null if no suitable Component can be found 538 * @see #getFirstComponent 539 * @throws IllegalArgumentException if aContainer is null 540 */ 541 public Component getDefaultComponent(Container aContainer) { 542 return getFirstComponent(aContainer); 543 } 544 545 /** 546 * Sets whether this ContainerOrderFocusTraversalPolicy transfers focus 547 * down-cycle implicitly. If {@code true}, during normal forward focus 548 * traversal, the Component traversed after a focus cycle root will be the 549 * focus-cycle-root's default Component to focus. If {@code false}, 550 * the next Component in the focus traversal cycle rooted at the specified 551 * focus cycle root will be traversed instead. The default value for this 552 * property is {@code true}. 553 * 554 * @param implicitDownCycleTraversal whether this 555 * ContainerOrderFocusTraversalPolicy transfers focus down-cycle 556 * implicitly 557 * @see #getImplicitDownCycleTraversal 558 * @see #getFirstComponent 559 */ 560 public void setImplicitDownCycleTraversal(boolean implicitDownCycleTraversal) { 561 this.implicitDownCycleTraversal = implicitDownCycleTraversal; 562 } 563 564 /** 565 * Returns whether this ContainerOrderFocusTraversalPolicy transfers focus 566 * down-cycle implicitly. If {@code true}, during normal forward focus 567 * traversal, the Component traversed after a focus cycle root will be the 568 * focus-cycle-root's default Component to focus. If {@code false}, 569 * the next Component in the focus traversal cycle rooted at the specified 570 * focus cycle root will be traversed instead. 571 * 572 * @return whether this ContainerOrderFocusTraversalPolicy transfers focus 573 * down-cycle implicitly 574 * @see #setImplicitDownCycleTraversal 575 * @see #getFirstComponent 576 */ 577 public boolean getImplicitDownCycleTraversal() { 578 return implicitDownCycleTraversal; 579 } 580 581 /** 582 * Determines whether a Component is an acceptable choice as the new 583 * focus owner. By default, this method will accept a Component if and 584 * only if it is visible, displayable, enabled, and focusable. 585 * 586 * @param aComponent the Component whose fitness as a focus owner is to 587 * be tested 588 * @return {@code true} if aComponent is visible, displayable, 589 * enabled, and focusable; {@code false} otherwise 590 */ 591 protected boolean accept(Component aComponent) { 592 if (!aComponent.canBeFocusOwner()) { 593 return false; 594 } 595 596 if (SunToolkit.isInstanceOf(aComponent, 597 "javax.swing.JToggleButton")) { 598 ButtonModel model = ((JToggleButton)aComponent).getModel(); 599 if (model != null) { 600 ButtonGroup group = model.getGroup(); 601 if (group != null) { 602 Enumeration<AbstractButton> elements = 603 group.getElements(); 604 int idx = 0; 605 while (group.getElements().hasMoreElements()) { 606 AbstractButton member = elements.nextElement(); 607 if (member instanceof JToggleButton && 608 member.isVisible() && member.isDisplayable() && 609 member.isEnabled() && member.isFocusable()) { 610 if (member == aComponent) { 611 return idx == 0; 612 } 613 idx++; 614 } 615 } 616 } 617 } 618 } 619 620 // Verify that the Component is recursively enabled. Disabling a 621 // heavyweight Container disables its children, whereas disabling 622 // a lightweight Container does not. 623 if (!(aComponent instanceof Window)) { 624 for (Container enableTest = aComponent.getParent(); 625 enableTest != null; 626 enableTest = enableTest.getParent()) 627 { 628 if (!(enableTest.isEnabled() || enableTest.isLightweight())) { 629 return false; 630 } 631 if (enableTest instanceof Window) { 632 break; 633 } 634 } 635 } 636 637 return true; 638 } 639 }