Skip to content

Commit 254d34c

Browse files
author
Semyon Sadetsky
committed
8074883: Tab key should move to focused button in a button group
Reviewed-by: alexsch, serb
1 parent fdb41a4 commit 254d34c

File tree

2 files changed

+210
-0
lines changed

2 files changed

+210
-0
lines changed

jdk/src/java.desktop/share/classes/javax/swing/JToggleButton.java

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
import java.io.ObjectOutputStream;
3636
import java.io.IOException;
37+
import java.util.Iterator;
3738

3839
/**
3940
* An implementation of a two-state button.
@@ -208,6 +209,96 @@ boolean shouldUpdateSelectedStateFromAction() {
208209
return true;
209210
}
210211

212+
private JToggleButton getGroupSelection(FocusEvent.Cause cause) {
213+
switch (cause) {
214+
case ACTIVATION:
215+
case TRAVERSAL:
216+
case TRAVERSAL_UP:
217+
case TRAVERSAL_DOWN:
218+
case TRAVERSAL_FORWARD:
219+
case TRAVERSAL_BACKWARD:
220+
ButtonModel model = getModel();
221+
JToggleButton selection = this;
222+
if (model instanceof DefaultButtonModel) {
223+
ButtonGroup group = ((DefaultButtonModel) model).getGroup();
224+
if (group != null && group.getSelection() != null
225+
&& !group.isSelected(model)) {
226+
Iterator<AbstractButton> iterator =
227+
group.getElements().asIterator();
228+
while (iterator.hasNext()) {
229+
AbstractButton member = iterator.next();
230+
if (group.isSelected(member.getModel())) {
231+
if (member instanceof JToggleButton &&
232+
member.isVisible() && member.isDisplayable() &&
233+
member.isEnabled() && member.isFocusable()) {
234+
selection = (JToggleButton) member;
235+
}
236+
break;
237+
}
238+
}
239+
}
240+
}
241+
return selection;
242+
default:
243+
return this;
244+
}
245+
}
246+
247+
/**
248+
* If this toggle button is a member of the {@link ButtonGroup} which has
249+
* another toggle button which is selected and can be the focus owner,
250+
* and the focus cause argument denotes window activation or focus
251+
* traversal action of any direction the result of the method execution
252+
* is the same as calling
253+
* {@link Component#requestFocus(FocusEvent.Cause)} on the toggle button
254+
* selected in the group.
255+
* In all other cases the result of the method is the same as calling
256+
* {@link Component#requestFocus(FocusEvent.Cause)} on this toggle button.
257+
*
258+
* @param cause the cause why the focus is requested
259+
* @see ButtonGroup
260+
* @see Component#requestFocus(FocusEvent.Cause)
261+
* @see FocusEvent.Cause
262+
*
263+
* @since 9
264+
*/
265+
@Override
266+
public void requestFocus(FocusEvent.Cause cause) {
267+
getGroupSelection(cause).requestFocusUnconditionally(cause);
268+
}
269+
270+
private void requestFocusUnconditionally(FocusEvent.Cause cause) {
271+
super.requestFocus(cause);
272+
}
273+
274+
/**
275+
* If this toggle button is a member of the {@link ButtonGroup} which has
276+
* another toggle button which is selected and can be the focus owner,
277+
* and the focus cause argument denotes window activation or focus
278+
* traversal action of any direction the result of the method execution
279+
* is the same as calling
280+
* {@link Component#requestFocusInWindow(FocusEvent.Cause)} on the toggle
281+
* button selected in the group.
282+
* In all other cases the result of the method is the same as calling
283+
* {@link Component#requestFocusInWindow(FocusEvent.Cause)} on this toggle
284+
* button.
285+
*
286+
* @param cause the cause why the focus is requested
287+
* @see ButtonGroup
288+
* @see Component#requestFocusInWindow(FocusEvent.Cause)
289+
* @see FocusEvent.Cause
290+
*
291+
* @since 9
292+
*/
293+
public boolean requestFocusInWindow(FocusEvent.Cause cause) {
294+
return getGroupSelection(cause)
295+
.requestFocusInWindowUnconditionally(cause);
296+
}
297+
298+
private boolean requestFocusInWindowUnconditionally(FocusEvent.Cause cause) {
299+
return super.requestFocusInWindow(cause);
300+
}
301+
211302
// *********************************************************************
212303

213304
/**
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* Copyright (c) 2016, 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.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/* @test
25+
* @bug 8074883
26+
* @summary Tab key should move to focused button in a button group
27+
* @run main ButtonGroupFocusTest
28+
*/
29+
30+
import javax.swing.*;
31+
import java.awt.*;
32+
import java.awt.event.KeyEvent;
33+
34+
public class ButtonGroupFocusTest {
35+
36+
private static JRadioButton button1;
37+
private static JRadioButton button2;
38+
private static JRadioButton button3;
39+
private static JRadioButton button4;
40+
private static JRadioButton button5;
41+
private static Robot robot;
42+
private static JFrame frame;
43+
44+
public static void main(String[] args) throws Exception {
45+
robot = new Robot();
46+
robot.setAutoDelay(100);
47+
48+
SwingUtilities.invokeAndWait(() -> {
49+
frame = new JFrame();
50+
Container contentPane = frame.getContentPane();
51+
contentPane.setLayout(new FlowLayout());
52+
button1 = new JRadioButton("Button 1");
53+
contentPane.add(button1);
54+
button2 = new JRadioButton("Button 2");
55+
contentPane.add(button2);
56+
button3 = new JRadioButton("Button 3");
57+
contentPane.add(button3);
58+
button4 = new JRadioButton("Button 4");
59+
contentPane.add(button4);
60+
button5 = new JRadioButton("Button 5");
61+
contentPane.add(button5);
62+
ButtonGroup group = new ButtonGroup();
63+
group.add(button1);
64+
group.add(button2);
65+
group.add(button3);
66+
67+
group = new ButtonGroup();
68+
group.add(button4);
69+
group.add(button5);
70+
71+
button2.setSelected(true);
72+
73+
frame.pack();
74+
frame.setVisible(true);
75+
});
76+
77+
robot.waitForIdle();
78+
robot.delay(200);
79+
80+
SwingUtilities.invokeAndWait(() -> {
81+
if( !button2.hasFocus() ) {
82+
frame.dispose();
83+
throw new RuntimeException(
84+
"Button 2 should get focus after activation");
85+
}
86+
});
87+
88+
robot.keyPress(KeyEvent.VK_TAB);
89+
robot.keyRelease(KeyEvent.VK_TAB);
90+
91+
robot.waitForIdle();
92+
robot.delay(200);
93+
94+
SwingUtilities.invokeAndWait(() -> {
95+
if( !button4.hasFocus() ) {
96+
frame.dispose();
97+
throw new RuntimeException(
98+
"Button 4 should get focus");
99+
}
100+
button3.setSelected(true);
101+
});
102+
103+
robot.keyPress(KeyEvent.VK_TAB);
104+
robot.keyRelease(KeyEvent.VK_TAB);
105+
106+
robot.waitForIdle();
107+
robot.delay(200);
108+
109+
SwingUtilities.invokeAndWait(() -> {
110+
if( !button3.hasFocus() ) {
111+
frame.dispose();
112+
throw new RuntimeException(
113+
"selected Button 3 should get focus");
114+
}
115+
});
116+
117+
SwingUtilities.invokeLater(frame::dispose);
118+
}
119+
}

0 commit comments

Comments
 (0)