changeset 6950:a419baadd1be

RT-36902: [ComboBox, DatePicker] Shift-Tab fails to traverse out of control
author jgiles
date Thu, 01 May 2014 11:16:36 +1200
parents a49dfbd3235c
children 35a1fc49c131
files modules/controls/src/main/java/com/sun/javafx/scene/control/skin/ComboBoxListViewSkin.java modules/controls/src/main/java/com/sun/javafx/scene/control/skin/DatePickerSkin.java modules/controls/src/test/java/javafx/scene/control/ComboBoxTest.java modules/controls/src/test/java/javafx/scene/control/DatePickerTest.java
diffstat 4 files changed, 159 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/ComboBoxListViewSkin.java	Thu May 01 08:31:17 2014 +1200
+++ b/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/ComboBoxListViewSkin.java	Thu May 01 11:16:36 2014 +1200
@@ -28,10 +28,10 @@
 import com.sun.javafx.scene.control.behavior.ComboBoxListViewBehavior;
 import java.util.List;
 
-import javafx.beans.InvalidationListener;
-import javafx.beans.Observable;
-import javafx.beans.value.ChangeListener;
-import javafx.beans.value.ObservableValue;
+import com.sun.javafx.scene.traversal.Algorithm;
+import com.sun.javafx.scene.traversal.Direction;
+import com.sun.javafx.scene.traversal.ParentTraversalEngine;
+import com.sun.javafx.scene.traversal.TraversalContext;
 import javafx.collections.FXCollections;
 import javafx.collections.ListChangeListener;
 import javafx.collections.ObservableList;
@@ -182,6 +182,21 @@
         
         // Fix for RT-19431 (also tested via ComboBoxListViewSkinTest)
         updateValue();
+
+        // Fix for RT-36902, where focus traversal was getting stuck inside the ComboBox
+        comboBox.setImpl_traversalEngine(new ParentTraversalEngine(comboBox, new Algorithm() {
+            @Override public Node select(Node owner, Direction dir, TraversalContext context) {
+                return null;
+            }
+
+            @Override public Node selectFirst(TraversalContext context) {
+                return null;
+            }
+
+            @Override public Node selectLast(TraversalContext context) {
+                return null;
+            }
+        }));
         
         registerChangeListener(comboBox.itemsProperty(), "ITEMS");
         registerChangeListener(comboBox.promptTextProperty(), "PROMPT_TEXT");
--- a/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/DatePickerSkin.java	Thu May 01 08:31:17 2014 +1200
+++ b/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/DatePickerSkin.java	Thu May 01 11:16:36 2014 +1200
@@ -32,20 +32,18 @@
 import java.time.chrono.HijrahChronology;
 import java.time.format.DateTimeParseException;
 
-import javafx.application.Platform;
+import com.sun.javafx.scene.traversal.Algorithm;
+import com.sun.javafx.scene.traversal.Direction;
+import com.sun.javafx.scene.traversal.ParentTraversalEngine;
+import com.sun.javafx.scene.traversal.TraversalContext;
 import javafx.beans.InvalidationListener;
 import javafx.beans.Observable;
-import javafx.beans.value.ChangeListener;
-import javafx.beans.value.ObservableValue;
 import javafx.css.PseudoClass;
-// import javafx.event.ActionEvent;
-import javafx.event.EventHandler;
 import javafx.geometry.Insets;
 import javafx.scene.Node;
 import javafx.scene.control.DatePicker;
 import javafx.scene.control.TextField;
 import javafx.scene.input.*;
-import javafx.scene.layout.Region;
 import javafx.util.StringConverter;
 
 import com.sun.javafx.scene.control.behavior.DatePickerBehavior;
@@ -156,6 +154,21 @@
             });
         }
 
+        // Fix for RT-36902, where focus traversal was getting stuck inside the DatePicker
+        datePicker.setImpl_traversalEngine(new ParentTraversalEngine(datePicker, new Algorithm() {
+            @Override public Node select(Node owner, Direction dir, TraversalContext context) {
+                return null;
+            }
+
+            @Override public Node selectFirst(TraversalContext context) {
+                return null;
+            }
+
+            @Override public Node selectLast(TraversalContext context) {
+                return null;
+            }
+        }));
+
         registerChangeListener(datePicker.chronologyProperty(), "CHRONOLOGY");
         registerChangeListener(datePicker.converterProperty(), "CONVERTER");
         registerChangeListener(datePicker.dayCellFactoryProperty(), "DAY_CELL_FACTORY");
--- a/modules/controls/src/test/java/javafx/scene/control/ComboBoxTest.java	Thu May 01 08:31:17 2014 +1200
+++ b/modules/controls/src/test/java/javafx/scene/control/ComboBoxTest.java	Thu May 01 11:16:36 2014 +1200
@@ -27,6 +27,7 @@
 
 import com.sun.javafx.scene.control.infrastructure.KeyModifier;
 import com.sun.javafx.scene.control.infrastructure.MouseEventFirer;
+import com.sun.javafx.scene.traversal.Direction;
 import com.sun.javafx.tk.Toolkit;
 import javafx.css.PseudoClass;
 
@@ -38,6 +39,7 @@
 
 import static com.sun.javafx.scene.control.infrastructure.ControlTestUtils.assertStyleClassContains;
 import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
 
 import java.util.*;
 
@@ -1612,4 +1614,55 @@
 
         sl.dispose();
     }
+
+    @Ignore("fix not yet developed")
+    @Test public void test_rt36902() {
+        final ComboBox<String> cb1 = new ComboBox<>(FXCollections.observableArrayList("a", "b", "c"));
+        final ComboBox<String> cb2 = new ComboBox<>(FXCollections.observableArrayList("a", "b", "c"));
+        cb2.setEditable(true);
+        VBox vbox = new VBox(cb1, cb2);
+
+        // lame - I would rather have one keyboard here but I couldn't get it to
+        // work, so watch out for which keyboard is used below
+        KeyEventFirer cb1Keyboard = new KeyEventFirer(cb1);
+        KeyEventFirer cb2Keyboard = new KeyEventFirer(cb2);
+
+        StageLoader sl = new StageLoader(vbox);
+        sl.getStage().requestFocus();
+        cb1.requestFocus();
+        Toolkit.getToolkit().firePulse();
+        Scene scene = sl.getStage().getScene();
+
+        assertTrue(cb1.isFocused());
+        assertEquals(cb1, scene.getFocusOwner());
+
+        // move focus forward to cb2
+        cb1Keyboard.doKeyPress(KeyCode.TAB);
+        assertTrue(cb2.isFocused());
+        assertEquals(cb2, scene.getFocusOwner());
+
+        // move focus forward again to cb1
+        cb2Keyboard.doKeyPress(KeyCode.TAB);
+        assertTrue(cb1.isFocused());
+        assertEquals(cb1, scene.getFocusOwner());
+
+        // now start going backwards with shift-tab.
+        // The first half of the bug is here - when we shift-tab into cb2, we
+        // actually go into the FakeFocusTextField subcomponent, so whilst the
+        // cb2.isFocused() returns true as expected, the scene focus owner is
+        // not the ComboBox, but the FakeFocusTextField inside it
+        cb1Keyboard.doKeyPress(KeyCode.TAB, KeyModifier.SHIFT);
+        assertTrue("Expect cb2 to be focused, but actual focus owner is: " + scene.getFocusOwner(),
+                cb2.isFocused());
+        assertEquals("Expect cb2 TextField to be focused, but actual focus owner is: " + scene.getFocusOwner(),
+                cb2.getEditor(), scene.getFocusOwner());
+
+        // This is where the second half of the bug appears, as we are stuck in
+        // the FakeFocusTextField of cb2, we never make it to cb1
+        cb2Keyboard.doKeyPress(KeyCode.TAB, KeyModifier.SHIFT);
+        assertTrue(cb1.isFocused());
+        assertEquals(cb1, scene.getFocusOwner());
+
+        sl.dispose();
+    }
 }
--- a/modules/controls/src/test/java/javafx/scene/control/DatePickerTest.java	Thu May 01 08:31:17 2014 +1200
+++ b/modules/controls/src/test/java/javafx/scene/control/DatePickerTest.java	Thu May 01 11:16:36 2014 +1200
@@ -29,14 +29,22 @@
 import java.time.chrono.*;
 import java.util.*;
 
+import com.sun.javafx.scene.control.infrastructure.KeyEventFirer;
+import com.sun.javafx.scene.control.infrastructure.KeyModifier;
+import com.sun.javafx.scene.control.infrastructure.StageLoader;
+import com.sun.javafx.tk.Toolkit;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.SimpleObjectProperty;
 import javafx.beans.property.SimpleStringProperty;
 import javafx.beans.property.StringProperty;
+import javafx.collections.FXCollections;
 import javafx.css.PseudoClass;
 import javafx.event.ActionEvent;
 import javafx.event.EventHandler;
 import javafx.scene.Node;
+import javafx.scene.Scene;
+import javafx.scene.input.KeyCode;
+import javafx.scene.layout.VBox;
 import javafx.util.Callback;
 import javafx.util.StringConverter;
 
@@ -49,6 +57,7 @@
 import org.junit.Test;
 
 import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
 
 public class DatePickerTest {
     private DatePicker datePicker;
@@ -392,4 +401,63 @@
         datePicker.getEditor().setText(converter.toString(datePicker.getValue()));
         assertEquals("5/22/2013", datePicker.getEditor().getText());
     }
+
+    @Ignore("fix not yet developed")
+    @Test public void test_rt36902() {
+        final DatePicker dp1 = new DatePicker() {
+            @Override public String toString() {
+                return "dp1";
+            }
+        };
+        final DatePicker dp2 = new DatePicker() {
+            @Override public String toString() {
+                return "dp2";
+            }
+        };
+        dp2.setEditable(true);
+        VBox vbox = new VBox(dp1, dp2);
+
+        // lame - I would rather have one keyboard here but I couldn't get it to
+        // work, so watch out for which keyboard is used below
+        KeyEventFirer dp1Keyboard = new KeyEventFirer(dp1);
+        KeyEventFirer dp2Keyboard = new KeyEventFirer(dp2);
+
+        StageLoader sl = new StageLoader(vbox);
+        sl.getStage().requestFocus();
+        dp1.requestFocus();
+        Toolkit.getToolkit().firePulse();
+        Scene scene = sl.getStage().getScene();
+
+        assertTrue(dp1.isFocused());
+        assertEquals(dp1, scene.getFocusOwner());
+
+        // move focus forward to dp2
+        dp1Keyboard.doKeyPress(KeyCode.TAB);
+        assertTrue(dp2.isFocused());
+        assertEquals(dp2, scene.getFocusOwner());
+
+        // move focus forward again to dp1
+        dp2Keyboard.doKeyPress(KeyCode.TAB);
+        assertTrue(dp1.isFocused());
+        assertEquals(dp1, scene.getFocusOwner());
+
+        // now start going backwards with shift-tab.
+        // The first half of the bug is here - when we shift-tab into dp2, we
+        // actually go into the FakeFocusTextField subcomponent, so whilst the
+        // dp2.isFocused() returns true as expected, the scene focus owner is
+        // not the ComboBox, but the FakeFocusTextField inside it
+        dp1Keyboard.doKeyPress(KeyCode.TAB, KeyModifier.SHIFT);
+        assertTrue("Expect dp2 to be focused, but actual focus owner is: " + scene.getFocusOwner(),
+                dp2.isFocused());
+        assertEquals("Expect dp2 TextField to be focused, but actual focus owner is: " + scene.getFocusOwner(),
+                dp2.getEditor(), scene.getFocusOwner());
+
+        // This is where the second half of the bug appears, as we are stuck in
+        // the FakeFocusTextField of dp2, we never make it to dp1
+        dp2Keyboard.doKeyPress(KeyCode.TAB, KeyModifier.SHIFT);
+        assertTrue(dp1.isFocused());
+        assertEquals(dp1, scene.getFocusOwner());
+
+        sl.dispose();
+    }
 }