1 /* 2 * Copyright (c) 2010, 2014, 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 javafx.scene; 27 28 29 import com.sun.javafx.pgstub.StubScene; 30 import com.sun.javafx.pgstub.StubToolkit; 31 import com.sun.javafx.tk.Toolkit; 32 import static org.junit.Assert.assertEquals; 33 import static org.junit.Assert.assertFalse; 34 import static org.junit.Assert.assertNull; 35 import static org.junit.Assert.assertSame; 36 import static org.junit.Assert.assertTrue; 37 import static org.junit.Assert.fail; 38 39 import java.util.ArrayList; 40 import java.util.List; 41 import javafx.beans.InvalidationListener; 42 import javafx.beans.Observable; 43 import javafx.beans.value.ChangeListener; 44 import javafx.beans.value.ObservableValue; 45 46 import javafx.scene.shape.Rectangle; 47 import javafx.stage.Stage; 48 49 import org.junit.After; 50 import org.junit.Before; 51 import org.junit.Test; 52 53 public class FocusTest { 54 55 private Stage stage; 56 private Scene scene; 57 private List<Node> nodes; 58 private int nodeIndex; 59 private StubToolkit toolkit; 60 private boolean actionTaken; 61 62 @Before 63 public void setUp() { 64 stage = new Stage(); 65 scene = new Scene(new Group(), 500, 500); 66 stage.setScene(scene); 67 stage.show(); 68 stage.requestFocus(); 69 nodes = new ArrayList(); 70 nodeIndex = 0; 71 72 toolkit = (StubToolkit) Toolkit.getToolkit(); 73 } 74 75 @After 76 public void tearDown() { 77 stage.hide(); 78 stage = null; 79 scene = null; 80 } 81 82 void fireTestPulse() { 83 // TODO: an actual pulse doesn't work in the stub environment. 84 // Just clean up focus instead. 85 scene.focusCleanup(); 86 } 87 88 boolean T = true; 89 boolean F = false; 90 91 Node n(boolean trav, boolean vis, boolean enable) { 92 Rectangle node = new Rectangle(); 93 node.setId("Rect-" + nodeIndex); 94 node.setFocusTraversable(trav); 95 node.setVisible(vis); 96 node.setDisable(!enable); 97 nodes.add(node); 98 nodeIndex++; 99 100 return node; 101 } 102 103 Node n() { 104 return n(T, T, T); 105 } 106 107 private void assertIsFocused(Scene s, Node n) { 108 assertEquals(n, s.getFocusOwner()); 109 assertTrue(n.isFocused()); 110 } 111 112 private void assertNotFocused(Scene s, Node n) { 113 assertTrue(n != s.getFocusOwner()); 114 assertFalse(n.isFocused()); 115 } 116 117 private void assertNullFocus(Scene s) { 118 assertNull(s.getFocusOwner()); 119 } 120 121 /** 122 * Test setting of initial focus. 123 */ 124 @Test 125 public void testInitial() { 126 assertNullFocus(scene); 127 scene.setRoot(new Group()); 128 scene.getRoot().getChildren().add(n()); 129 fireTestPulse(); 130 assertIsFocused(scene, nodes.get(0)); 131 } 132 133 /** 134 * Test requestFocus() on eligible and ineligible nodes. 135 */ 136 @Test 137 public void testRequest() { 138 scene.setRoot(new Group()); 139 scene.getRoot().getChildren().addAll( 140 n(T, T, T), // 0 - focusable 141 n(F, T, T), // 1 - focusable 142 n(T, T, F), // 2 - not focusable 143 n(F, T, F), // 3 - not focusable 144 n(T, F, T), // 4 - not focusable 145 n(F, F, T), // 5 - not focusable 146 n(T, F, F), // 6 - not focusable 147 n(F, F, F) // 7 - not focusable 148 ); 149 n(); // 8 - not focusable, because not in scene 150 151 nodes.get(0).requestFocus(); 152 assertIsFocused(scene, nodes.get(0)); 153 nodes.get(1).requestFocus(); 154 assertIsFocused(scene, nodes.get(1)); 155 nodes.get(2).requestFocus(); 156 assertNotFocused(scene, nodes.get(2)); 157 nodes.get(3).requestFocus(); 158 assertNotFocused(scene, nodes.get(3)); 159 nodes.get(4).requestFocus(); 160 assertNotFocused(scene, nodes.get(4)); 161 nodes.get(5).requestFocus(); 162 assertNotFocused(scene, nodes.get(5)); 163 nodes.get(6).requestFocus(); 164 assertNotFocused(scene, nodes.get(6)); 165 nodes.get(7).requestFocus(); 166 assertNotFocused(scene, nodes.get(7)); 167 nodes.get(8).requestFocus(); 168 assertNotFocused(scene, nodes.get(8)); 169 } 170 171 /** 172 * Test removing the focus owner without another eligible node in the scene. 173 */ 174 @Test 175 public void testRemove1() { 176 scene.setRoot(new Group()); 177 scene.getRoot().getChildren().add(n()); 178 nodes.get(0).requestFocus(); 179 scene.getRoot().getChildren().remove(nodes.get(0)); 180 fireTestPulse(); 181 assertNotFocused(scene, nodes.get(0)); 182 assertNullFocus(scene); 183 } 184 185 /** 186 * Test removing the focus owner with another eligible node in the scene. 187 */ 188 @Test 189 public void testRemove2() { 190 scene.setRoot(new Group()); 191 scene.getRoot().getChildren().addAll(n(), n()); 192 nodes.get(0).requestFocus(); 193 scene.getRoot().getChildren().remove(nodes.get(0)); 194 fireTestPulse(); 195 assertNotFocused(scene, nodes.get(0)); 196 assertIsFocused(scene, nodes.get(1)); 197 } 198 199 /** 200 * Test removing the focus owner without another eligible node in the scene. 201 */ 202 @Test 203 public void testRemove_ClearsFocusOnRemovedNode1() { 204 Node n = n(); 205 scene.setRoot(new Group()); 206 scene.getRoot().getChildren().add(n); 207 n.requestFocus(); 208 scene.getRoot().getChildren().remove(n); 209 fireTestPulse(); 210 assertNotFocused(scene, n); 211 } 212 213 /** 214 * Test removing the focus owner with another eligible node in the scene. 215 */ 216 @Test 217 public void testRemove_ClearsFocusOnRemovedNode2() { 218 Node n = n(); 219 scene.setRoot(new Group()); 220 scene.getRoot().getChildren().addAll(n, n()); 221 n.requestFocus(); 222 scene.getRoot().getChildren().remove(n); 223 fireTestPulse(); 224 assertNotFocused(scene, n); 225 assertIsFocused(scene, scene.getRoot().getChildren().get(0)); 226 } 227 228 /** 229 * Test removing the focus owner without another eligible node in the scene. 230 */ 231 @Test 232 public void testRemoveChildOfGroup_ClearsFocusOnRemovedNode1() { 233 Node n = n(); 234 Group g = new Group(n); 235 scene.setRoot(new Group()); 236 scene.getRoot().getChildren().add(g); 237 n.requestFocus(); 238 g.getChildren().remove(n); 239 fireTestPulse(); 240 assertNotFocused(scene, n); 241 } 242 243 /** 244 * Test making the focus owner invisible without another eligible 245 * node in the scene. 246 */ 247 @Test 248 public void testInvisible1() { 249 scene.setRoot(new Group()); 250 scene.getRoot().getChildren().add(n()); 251 nodes.get(0).requestFocus(); 252 assertIsFocused(scene, nodes.get(0)); 253 nodes.get(0).setVisible(false); 254 fireTestPulse(); 255 assertNotFocused(scene, nodes.get(0)); 256 assertNullFocus(scene); 257 } 258 259 /** 260 * Test making the focus owner invisible with another eligible 261 * node in the scene. 262 */ 263 @Test 264 public void testInvisible2() { 265 scene.setRoot(new Group()); 266 scene.getRoot().getChildren().addAll(n(), n()); 267 nodes.get(0).requestFocus(); 268 assertIsFocused(scene, nodes.get(0)); 269 nodes.get(0).setVisible(false); 270 fireTestPulse(); 271 assertNotFocused(scene, nodes.get(0)); 272 assertIsFocused(scene, nodes.get(1)); 273 } 274 275 /** 276 * Test making the focus owner disabled without another eligible 277 * node in the scene. 278 */ 279 @Test 280 public void testDisable1() { 281 scene.setRoot(new Group()); 282 scene.getRoot().getChildren().add(n()); 283 nodes.get(0).requestFocus(); 284 nodes.get(0).setDisable(true); 285 fireTestPulse(); 286 assertNotFocused(scene, nodes.get(0)); 287 assertNullFocus(scene); 288 } 289 290 /** 291 * Test making the focus owner disabled with another eligible 292 * node in the scene. 293 */ 294 @Test 295 public void testDisable2() { 296 scene.setRoot(new Group()); 297 scene.getRoot().getChildren().addAll(n(), n()); 298 nodes.get(0).requestFocus(); 299 nodes.get(0).setDisable(true); 300 fireTestPulse(); 301 assertNotFocused(scene, nodes.get(0)); 302 assertIsFocused(scene, nodes.get(1)); 303 } 304 305 /** 306 * When focus null, test adding an eligible, traversable node. 307 */ 308 @Test 309 public void testAddEligible() { 310 fireTestPulse(); // make sure focus is clean 311 assertNullFocus(scene); 312 scene.setRoot(new Group()); 313 scene.getRoot().getChildren().add(n()); 314 fireTestPulse(); 315 assertIsFocused(scene, nodes.get(0)); 316 } 317 318 /** 319 * When focus null, test making a node traversable. 320 */ 321 @Test 322 public void testBecomeTraversable() { 323 scene.setRoot(new Group()); 324 scene.getRoot().getChildren().addAll(n(F, T, T), n(F, T, T)); 325 fireTestPulse(); 326 assertNullFocus(scene); 327 toolkit.clearPulseRequested(); 328 assertFalse(toolkit.isPulseRequested()); 329 nodes.get(0).setFocusTraversable(true); 330 assertTrue(toolkit.isPulseRequested()); 331 fireTestPulse(); 332 assertIsFocused(scene, nodes.get(0)); 333 } 334 335 /** 336 * When focus null, test making a node visible. 337 */ 338 @Test 339 public void testBecomeVisible() { 340 scene.setRoot(new Group()); 341 scene.getRoot().getChildren().add(n(T, F, T)); 342 fireTestPulse(); 343 assertNullFocus(scene); 344 nodes.get(0).setVisible(true); 345 fireTestPulse(); 346 assertIsFocused(scene, nodes.get(0)); 347 } 348 349 /** 350 * When focus null, test making a node enabled. 351 */ 352 @Test 353 public void testBecomeEnabled() { 354 scene.setRoot(new Group()); 355 scene.getRoot().getChildren().add(n(T, T, F)); 356 fireTestPulse(); 357 assertNullFocus(scene); 358 nodes.get(0).setDisable(false); 359 fireTestPulse(); 360 assertIsFocused(scene, nodes.get(0)); 361 } 362 363 /** 364 * When focus exists, test adding an eligible, traversable node. 365 */ 366 @Test 367 public void testAddEligible2() { 368 scene.setRoot(new Group()); 369 scene.getRoot().getChildren().add(n()); 370 fireTestPulse(); 371 assertIsFocused(scene, nodes.get(0)); 372 scene.getRoot().getChildren().add(n()); 373 fireTestPulse(); 374 assertIsFocused(scene, nodes.get(0)); 375 } 376 377 /** 378 * When focus exists, test making a node traversable. 379 */ 380 @Test 381 public void testBecomeTraversable2() { 382 scene.setRoot(new Group()); 383 scene.getRoot().getChildren().addAll(n(), n(F, T, T)); 384 fireTestPulse(); 385 assertIsFocused(scene, nodes.get(0)); 386 nodes.get(1).setFocusTraversable(true); 387 fireTestPulse(); 388 assertIsFocused(scene, nodes.get(0)); 389 } 390 391 /** 392 * When focus exists, test making a node visible. 393 */ 394 @Test 395 public void testBecomeVisible2() { 396 scene.setRoot(new Group()); 397 scene.getRoot().getChildren().addAll(n(), n(T, F, T)); 398 fireTestPulse(); 399 assertIsFocused(scene, nodes.get(0)); 400 nodes.get(1).setVisible(true); 401 fireTestPulse(); 402 assertIsFocused(scene, nodes.get(0)); 403 } 404 405 /** 406 * When focus exists, test making a node enabled. 407 */ 408 @Test 409 public void testBecomeEnabled2() { 410 scene.setRoot(new Group()); 411 scene.getRoot().getChildren().addAll(n(), n(T, T, F)); 412 fireTestPulse(); 413 assertIsFocused(scene, nodes.get(0)); 414 nodes.get(1).setDisable(false); 415 fireTestPulse(); 416 assertIsFocused(scene, nodes.get(0)); 417 } 418 419 /** 420 * Test moving the focus within a scene. 421 */ 422 @Test 423 public void testMoveWithinScene() { 424 Group g1 = new Group(n()); 425 Group g2 = new Group(); 426 scene.setRoot(new Group()); 427 scene.getRoot().getChildren().addAll(g1, g2); 428 fireTestPulse(); 429 assertIsFocused(scene, nodes.get(0)); 430 g2.getChildren().add(nodes.get(0)); 431 fireTestPulse(); 432 assertIsFocused(scene, nodes.get(0)); 433 } 434 435 /** 436 * Test moving the focus into an invisible group, with no other 437 * eligible nodes in the scene. 438 */ 439 @Test 440 public void testMoveIntoInvisible() { 441 Group g1 = new Group(n()); 442 Group g2 = new Group(); 443 g2.setVisible(false); 444 scene.setRoot(new Group()); 445 scene.getRoot().getChildren().addAll(g1, g2); 446 fireTestPulse(); 447 assertIsFocused(scene, nodes.get(0)); 448 g2.getChildren().add(nodes.get(0)); 449 fireTestPulse(); 450 assertNullFocus(scene); 451 } 452 453 /** 454 * Test moving the focus into an invisible group, with another 455 * eligible node in the scene. 456 */ 457 @Test 458 public void testMoveIntoInvisible2() { 459 Group g1 = new Group(n()); 460 Group g2 = new Group(); 461 g2.setVisible(false); 462 scene.setRoot(new Group()); 463 scene.getRoot().getChildren().addAll(g1, g2, n()); 464 nodes.get(0).requestFocus(); 465 fireTestPulse(); 466 assertIsFocused(scene, nodes.get(0)); 467 g2.getChildren().add(nodes.get(0)); 468 fireTestPulse(); 469 assertIsFocused(scene, nodes.get(1)); 470 } 471 472 /** 473 * Test moving the focus into a disabled group, with no other 474 * eligible nodes in the scene. 475 */ 476 @Test 477 public void testMoveIntoDisabled() { 478 Group g1 = new Group(n()); 479 Group g2 = new Group(); 480 g2.setDisable(true); 481 scene.setRoot(new Group()); 482 scene.getRoot().getChildren().addAll(g1, g2); 483 fireTestPulse(); 484 assertIsFocused(scene, nodes.get(0)); 485 g2.getChildren().add(nodes.get(0)); 486 fireTestPulse(); 487 assertNullFocus(scene); 488 } 489 490 /** 491 * Test moving the focus into a disabled group, with another 492 * eligible nodes in the scene. 493 */ 494 @Test 495 public void testMoveIntoDisabled2() { 496 Group g1 = new Group(n()); 497 Group g2 = new Group(); 498 g2.setDisable(true); 499 scene.setRoot(new Group()); 500 scene.getRoot().getChildren().addAll(g1, g2, n()); 501 nodes.get(0).requestFocus(); 502 fireTestPulse(); 503 assertIsFocused(scene, nodes.get(0)); 504 g2.getChildren().add(nodes.get(0)); 505 fireTestPulse(); 506 assertIsFocused(scene, nodes.get(1)); 507 } 508 509 /** 510 * Test making the parent of focused node disabled, with no other 511 * eligible nodes in the scene. 512 */ 513 @Test 514 public void testMakeParentDisabled() { 515 Group g1 = new Group(n()); 516 Group g2 = new Group(); 517 g2.setVisible(false); 518 scene.setRoot(new Group()); 519 scene.getRoot().getChildren().addAll(g1, g2); 520 fireTestPulse(); 521 assertIsFocused(scene, nodes.get(0)); 522 g1.setDisable(true); 523 fireTestPulse(); 524 assertNotFocused(scene, nodes.get(0)); 525 assertNullFocus(scene); 526 } 527 528 /** 529 * Test making the parent of focused node disabled, with another 530 * eligible nodes in the scene. 531 */ 532 @Test 533 public void testMakeParentDisabled2() { 534 Group g1 = new Group(n()); 535 Group g2 = new Group(); 536 g2.setVisible(false); 537 scene.setRoot(new Group()); 538 scene.getRoot().getChildren().addAll(g1, g2, n()); 539 nodes.get(0).requestFocus(); 540 fireTestPulse(); 541 assertIsFocused(scene, nodes.get(0)); 542 g1.setDisable(true); 543 fireTestPulse(); 544 assertNotFocused(scene, nodes.get(0)); 545 assertIsFocused(scene, nodes.get(1)); 546 } 547 548 /** 549 * Test making the parent of focused node invisible, with no other 550 * eligible nodes in the scene. 551 */ 552 @Test 553 public void testMakeParentInvisible() { 554 Group g1 = new Group(n()); 555 Group g2 = new Group(); 556 g2.setVisible(false); 557 scene.setRoot(new Group()); 558 scene.getRoot().getChildren().addAll(g1, g2); 559 fireTestPulse(); 560 assertIsFocused(scene, nodes.get(0)); 561 g1.setVisible(false); 562 fireTestPulse(); 563 assertNotFocused(scene, nodes.get(0)); 564 assertNullFocus(scene); 565 } 566 567 /** 568 * Test making the parent of focused node invisible, with another 569 * eligible nodes in the scene. 570 */ 571 @Test 572 public void testMakeParentInvisible2() { 573 Group g1 = new Group(n()); 574 Group g2 = new Group(); 575 g2.setVisible(false); 576 scene.setRoot(new Group()); 577 scene.getRoot().getChildren().addAll(g1, g2, n()); 578 nodes.get(0).requestFocus(); 579 fireTestPulse(); 580 assertIsFocused(scene, nodes.get(0)); 581 g1.setVisible(false); 582 fireTestPulse(); 583 assertNotFocused(scene, nodes.get(0)); 584 assertIsFocused(scene, nodes.get(1)); 585 } 586 587 /** 588 * Focus should not move if stacking order changes. 589 */ 590 @Test 591 public void testToFront() { 592 scene.setRoot(new Group()); 593 scene.getRoot().getChildren().addAll(n(), n()); 594 nodes.get(0).requestFocus(); 595 assertIsFocused(scene, nodes.get(0)); 596 assertNotFocused(scene, nodes.get(1)); 597 nodes.get(0).toFront(); 598 assertIsFocused(scene, nodes.get(0)); 599 assertNotFocused(scene, nodes.get(1)); 600 nodes.get(0).toBack(); 601 assertIsFocused(scene, nodes.get(0)); 602 assertNotFocused(scene, nodes.get(1)); 603 } 604 605 /** 606 * Test moving focused node into scene which is not in active stage. 607 */ 608 @Test 609 public void testMoveIntoInactiveScene() { 610 scene.setRoot(new Group()); 611 scene.getRoot().getChildren().add(n()); 612 nodes.get(0).requestFocus(); 613 assertIsFocused(scene, nodes.get(0)); 614 Scene scene2 = new Scene(new Group()); 615 scene2.getRoot().getChildren().add(nodes.get(0)); 616 fireTestPulse(); 617 assertNullFocus(scene); 618 assertNullFocus(scene2); 619 nodes.get(0).requestFocus(); 620 fireTestPulse(); 621 assertNullFocus(scene); 622 assertEquals(nodes.get(0), scene2.getFocusOwner()); 623 assertFalse(nodes.get(0).isFocused()); 624 stage.setScene(scene2); 625 fireTestPulse(); 626 assertNullFocus(scene); 627 assertIsFocused(scene2, nodes.get(0)); 628 } 629 630 /** 631 * Test making stage invisible. 632 */ 633 @Test 634 public void testInvisibleStage() { 635 scene.setRoot(new Group()); 636 scene.getRoot().getChildren().add(n()); 637 nodes.get(0).requestFocus(); 638 stage.show(); 639 fireTestPulse(); 640 assertIsFocused(scene, nodes.get(0)); 641 stage.hide(); 642 nodes.get(0).requestFocus(); 643 fireTestPulse(); 644 assertEquals(nodes.get(0), scene.getFocusOwner()); 645 assertFalse(nodes.get(0).isFocused()); 646 } 647 648 /** 649 * Test switching two scenes in one stage. Focus owner in scenes should 650 * remain the same while focused node should change. 651 */ 652 @Test 653 public void testSwitchScenes(){ 654 scene.setRoot(new Group()); 655 scene.getRoot().getChildren().add(n()); 656 nodes.get(0).requestFocus(); 657 Scene scene2 = new Scene(new Group()); 658 scene2.getRoot().getChildren().add(n()); 659 nodes.get(1).requestFocus(); 660 fireTestPulse(); 661 assertIsFocused(scene, nodes.get(0)); 662 assertFalse(nodes.get(1).isFocused()); 663 assertEquals(nodes.get(1), scene2.getFocusOwner()); 664 stage.setScene(scene2); 665 fireTestPulse(); 666 assertFalse(nodes.get(0).isFocused()); 667 assertEquals(nodes.get(0), scene.getFocusOwner()); 668 assertIsFocused(scene2, nodes.get(1)); 669 } 670 671 @Test public void nestedFocusRequestsShouldResultInOneFocusedNode() { 672 final Node n1 = n(); 673 final Node n2 = n(); 674 scene.setRoot(new Group(n1, n2)); 675 676 n1.focusedProperty().addListener((ov, lostFocus, getFocus) -> { 677 if (lostFocus) { 678 n1.requestFocus(); 679 } 680 }); 681 682 n2.focusedProperty().addListener(o -> { 683 // n2 can be invalidated, but should not have focus 684 assertTrue(n1.isFocused()); 685 assertFalse(n2.isFocused()); 686 }); 687 688 n2.focusedProperty().addListener((ov, lostFocus, getFocus) -> fail("n2 should never get focus")); 689 690 stage.show(); 691 n1.requestFocus(); 692 assertTrue(n1.isFocused()); 693 assertFalse(n2.isFocused()); 694 695 n2.requestFocus(); 696 assertTrue(n1.isFocused()); 697 assertFalse(n2.isFocused()); 698 } 699 700 @Test public void shouldCancelInputMethodWhenLoosingFocus() { 701 final Node n1 = n(); 702 final Node n2 = n(); 703 scene.setRoot(new Group(n1, n2)); 704 705 stage.show(); 706 707 Toolkit.getToolkit().firePulse(); 708 709 n1.requestFocus(); 710 assertSame(n1, scene.getFocusOwner()); 711 actionTaken = false; 712 713 ((StubScene) scene.impl_getPeer()).setInputMethodCompositionFinishDelegate( 714 () -> { 715 assertSame(n1, scene.getFocusOwner()); 716 actionTaken = true; 717 } 718 ); 719 720 n2.requestFocus(); 721 722 ((StubScene) scene.impl_getPeer()).setInputMethodCompositionFinishDelegate( 723 null); 724 725 assertSame(n2, scene.getFocusOwner()); 726 assertTrue(actionTaken); 727 } 728 729 // TODO: tests for moving nodes between scenes 730 // and active and inactive stages 731 }