8074883: Tab key should move to focused button in a button group
authorssadetsky
Tue, 20 Dec 2016 11:53:07 +0300
changeset 42941 040a43b1c909
parent 42940 0d1409532a41
child 42942 ca9965624c3b
child 43072 6f7769361c22
8074883: Tab key should move to focused button in a button group Reviewed-by: alexsch, serb
jdk/src/java.desktop/share/classes/javax/swing/JToggleButton.java
jdk/test/javax/swing/JRadioButton/ButtonGroupFocus/ButtonGroupFocusTest.java
--- a/jdk/src/java.desktop/share/classes/javax/swing/JToggleButton.java	Mon Dec 19 15:33:32 2016 -0800
+++ b/jdk/src/java.desktop/share/classes/javax/swing/JToggleButton.java	Tue Dec 20 11:53:07 2016 +0300
@@ -34,6 +34,7 @@
 
 import java.io.ObjectOutputStream;
 import java.io.IOException;
+import java.util.Iterator;
 
 /**
  * An implementation of a two-state button.
@@ -208,6 +209,96 @@
         return true;
     }
 
+    private JToggleButton getGroupSelection(FocusEvent.Cause cause) {
+        switch (cause) {
+          case ACTIVATION:
+          case TRAVERSAL:
+          case TRAVERSAL_UP:
+          case TRAVERSAL_DOWN:
+          case TRAVERSAL_FORWARD:
+          case TRAVERSAL_BACKWARD:
+            ButtonModel model = getModel();
+            JToggleButton selection = this;
+            if (model instanceof DefaultButtonModel) {
+                ButtonGroup group = ((DefaultButtonModel) model).getGroup();
+                if (group != null && group.getSelection() != null
+                                                  && !group.isSelected(model)) {
+                    Iterator<AbstractButton> iterator =
+                                               group.getElements().asIterator();
+                    while (iterator.hasNext()) {
+                        AbstractButton member = iterator.next();
+                        if (group.isSelected(member.getModel())) {
+                            if (member instanceof JToggleButton &&
+                                member.isVisible() && member.isDisplayable() &&
+                                member.isEnabled() && member.isFocusable()) {
+                                selection = (JToggleButton) member;
+                            }
+                            break;
+                        }
+                    }
+                }
+            }
+            return selection;
+          default:
+            return this;
+        }
+    }
+
+    /**
+     * If this toggle button is a member of the {@link ButtonGroup} which has
+     * another toggle button which is selected and can be the focus owner,
+     * and the focus cause argument denotes window activation or focus
+     * traversal action of any direction the result of the method execution
+     * is the same as calling
+     * {@link Component#requestFocus(FocusEvent.Cause)} on the toggle button
+     * selected in the group.
+     * In all other cases the result of the method is the same as calling
+     * {@link Component#requestFocus(FocusEvent.Cause)} on this toggle button.
+     *
+     * @param  cause the cause why the focus is requested
+     * @see ButtonGroup
+     * @see Component#requestFocus(FocusEvent.Cause)
+     * @see FocusEvent.Cause
+     *
+     * @since 9
+     */
+    @Override
+    public void requestFocus(FocusEvent.Cause cause) {
+        getGroupSelection(cause).requestFocusUnconditionally(cause);
+    }
+
+    private void requestFocusUnconditionally(FocusEvent.Cause cause) {
+        super.requestFocus(cause);
+    }
+
+    /**
+     * If this toggle button is a member of the {@link ButtonGroup} which has
+     * another toggle button which is selected and can be the focus owner,
+     * and the focus cause argument denotes window activation or focus
+     * traversal action of any direction the result of the method execution
+     * is the same as calling
+     * {@link Component#requestFocusInWindow(FocusEvent.Cause)} on the toggle
+     * button selected in the group.
+     * In all other cases the result of the method is the same as calling
+     * {@link Component#requestFocusInWindow(FocusEvent.Cause)} on this toggle
+     * button.
+     *
+     * @param  cause the cause why the focus is requested
+     * @see ButtonGroup
+     * @see Component#requestFocusInWindow(FocusEvent.Cause)
+     * @see FocusEvent.Cause
+     *
+     * @since 9
+     */
+    public boolean requestFocusInWindow(FocusEvent.Cause cause) {
+        return getGroupSelection(cause)
+                                    .requestFocusInWindowUnconditionally(cause);
+    }
+
+    private boolean requestFocusInWindowUnconditionally(FocusEvent.Cause cause) {
+        return super.requestFocusInWindow(cause);
+    }
+
     // *********************************************************************
 
     /**
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/swing/JRadioButton/ButtonGroupFocus/ButtonGroupFocusTest.java	Tue Dec 20 11:53:07 2016 +0300
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/* @test
+ * @bug 8074883
+ * @summary Tab key should move to focused button in a button group
+ * @run main ButtonGroupFocusTest
+ */
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.KeyEvent;
+
+public class ButtonGroupFocusTest {
+
+    private static JRadioButton button1;
+    private static JRadioButton button2;
+    private static JRadioButton button3;
+    private static JRadioButton button4;
+    private static JRadioButton button5;
+    private static Robot robot;
+    private static JFrame frame;
+
+    public static void main(String[] args) throws Exception {
+        robot = new Robot();
+        robot.setAutoDelay(100);
+
+        SwingUtilities.invokeAndWait(() -> {
+            frame = new JFrame();
+            Container contentPane = frame.getContentPane();
+            contentPane.setLayout(new FlowLayout());
+            button1 = new JRadioButton("Button 1");
+            contentPane.add(button1);
+            button2 = new JRadioButton("Button 2");
+            contentPane.add(button2);
+            button3 = new JRadioButton("Button 3");
+            contentPane.add(button3);
+            button4 = new JRadioButton("Button 4");
+            contentPane.add(button4);
+            button5 = new JRadioButton("Button 5");
+            contentPane.add(button5);
+            ButtonGroup group = new ButtonGroup();
+            group.add(button1);
+            group.add(button2);
+            group.add(button3);
+
+            group = new ButtonGroup();
+            group.add(button4);
+            group.add(button5);
+
+            button2.setSelected(true);
+
+            frame.pack();
+            frame.setVisible(true);
+        });
+
+        robot.waitForIdle();
+        robot.delay(200);
+
+        SwingUtilities.invokeAndWait(() -> {
+            if( !button2.hasFocus() ) {
+                frame.dispose();
+                throw new RuntimeException(
+                        "Button 2 should get focus after activation");
+            }
+        });
+
+        robot.keyPress(KeyEvent.VK_TAB);
+        robot.keyRelease(KeyEvent.VK_TAB);
+
+        robot.waitForIdle();
+        robot.delay(200);
+
+        SwingUtilities.invokeAndWait(() -> {
+            if( !button4.hasFocus() ) {
+                frame.dispose();
+                throw new RuntimeException(
+                        "Button 4 should get focus");
+            }
+            button3.setSelected(true);
+        });
+
+        robot.keyPress(KeyEvent.VK_TAB);
+        robot.keyRelease(KeyEvent.VK_TAB);
+
+        robot.waitForIdle();
+        robot.delay(200);
+
+        SwingUtilities.invokeAndWait(() -> {
+            if( !button3.hasFocus() ) {
+                frame.dispose();
+                throw new RuntimeException(
+                        "selected Button 3 should get focus");
+            }
+        });
+
+        SwingUtilities.invokeLater(frame::dispose);
+    }
+}