8074883: Tab key should move to focused button in a button group
Reviewed-by: alexsch, serb
--- 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);
+ }
+}