Skip to content

Commit 86ecd5b

Browse files
author
Kazuhisa Takakuri
committed
8154043: Fields not reachable anymore by tab-key, because of new tabbing behaviour of radio button groups.
8182577: Exception when Tab key moves focus to a JCheckbox with a custom ButtonModel Reviewed-by: phh, andrew Backport-of: 8d81ec63b2bafc431cbb8572a3e45e76580ab46f
1 parent 942d6d0 commit 86ecd5b

File tree

5 files changed

+289
-34
lines changed

5 files changed

+289
-34
lines changed

jdk/src/share/classes/javax/swing/LayoutFocusTraversalPolicy.java

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2000, 2008, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -26,9 +26,9 @@
2626

2727
import java.awt.Component;
2828
import java.awt.Container;
29-
import java.awt.ComponentOrientation;
3029
import java.util.Comparator;
3130
import java.io.*;
31+
import java.util.Enumeration;
3232
import sun.awt.SunToolkit;
3333

3434

@@ -235,6 +235,30 @@ protected boolean accept(Component aComponent) {
235235
JComboBox box = (JComboBox)aComponent;
236236
return box.getUI().isFocusTraversable(box);
237237
} else if (aComponent instanceof JComponent) {
238+
if (SunToolkit.isInstanceOf(aComponent,
239+
"javax.swing.JToggleButton")) {
240+
ButtonModel buttonModel = ((JToggleButton) aComponent).getModel();
241+
if (buttonModel != null && buttonModel instanceof DefaultButtonModel) {
242+
DefaultButtonModel model = (DefaultButtonModel) buttonModel;
243+
ButtonGroup group = model.getGroup();
244+
if (group != null) {
245+
Enumeration<AbstractButton> elements =
246+
group.getElements();
247+
int idx = 0;
248+
while (elements.hasMoreElements()) {
249+
AbstractButton member = elements.nextElement();
250+
if (member.isVisible() && member.isDisplayable() &&
251+
member.isEnabled() && member.isFocusable()) {
252+
if (member == aComponent) {
253+
return idx == 0;
254+
}
255+
idx++;
256+
}
257+
}
258+
}
259+
}
260+
}
261+
238262
JComponent jComponent = (JComponent)aComponent;
239263
InputMap inputMap = jComponent.getInputMap(JComponent.WHEN_FOCUSED,
240264
false);

jdk/src/share/classes/javax/swing/plaf/basic/BasicRadioButtonUI.java

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -437,21 +437,7 @@ boolean containsInGroup(Object obj){
437437
// Check if the next object to gain focus belongs
438438
// to the button group or not
439439
Component getFocusTransferBaseComponent(boolean next){
440-
Component focusBaseComp = activeBtn;
441-
Container container = focusBaseComp.getFocusCycleRootAncestor();
442-
if (container != null) {
443-
FocusTraversalPolicy policy = container.getFocusTraversalPolicy();
444-
Component comp = next ? policy.getComponentAfter(container, activeBtn)
445-
: policy.getComponentBefore(container, activeBtn);
446-
447-
// If next component in the button group, use last/first button as base focus
448-
// otherwise, use the activeBtn as the base focus
449-
if (containsInGroup(comp)) {
450-
focusBaseComp = next ? lastBtn : firstBtn;
451-
}
452-
}
453-
454-
return focusBaseComp;
440+
return firstBtn;
455441
}
456442

457443
boolean getButtonGroupInfo() {
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
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+
/*
25+
@test
26+
@bug 8154043
27+
@summary Fields not reachable anymore by tab-key, because of new tabbing
28+
behaviour of radio button groups.
29+
@run main ButtonGroupLayoutTraversalTest
30+
*/
31+
32+
import javax.swing.*;
33+
import java.awt.*;
34+
import java.awt.event.FocusAdapter;
35+
import java.awt.event.FocusEvent;
36+
import java.awt.event.KeyEvent;
37+
38+
public class ButtonGroupLayoutTraversalTest {
39+
static int nx = 3;
40+
static int ny = 3;
41+
42+
static int focusCnt[] = new int[nx * ny];
43+
private static JFrame window;
44+
45+
46+
public static void main(String[] args) throws Exception {
47+
48+
SwingUtilities.invokeAndWait(()->initLayout(nx, ny));
49+
Robot robot = new Robot();
50+
robot.setAutoDelay(100);
51+
robot.waitForIdle();
52+
robot.delay(200);
53+
54+
55+
for(int i = 0; i < nx * ny - nx * ny / 2 - 1; i++) {
56+
robot.keyPress(KeyEvent.VK_RIGHT);
57+
robot.keyRelease(KeyEvent.VK_RIGHT);
58+
}
59+
60+
for(int i = 0; i < nx * ny / 2; i++) {
61+
robot.keyPress(KeyEvent.VK_TAB);
62+
robot.keyRelease(KeyEvent.VK_TAB);
63+
}
64+
65+
robot.waitForIdle();
66+
robot.delay(200);
67+
68+
for(int i = 0; i < nx * ny; i++) {
69+
if(focusCnt[i] < 1) {
70+
SwingUtilities.invokeLater(window::dispose);
71+
throw new RuntimeException("Component " + i +
72+
" is not reachable in the forward focus cycle");
73+
} else if (focusCnt[i] > 1) {
74+
SwingUtilities.invokeLater(window::dispose);
75+
throw new RuntimeException("Component " + i +
76+
" got focus more than once in the forward focus cycle");
77+
}
78+
}
79+
80+
for(int i = 0; i < nx * ny / 2; i++) {
81+
robot.keyPress(KeyEvent.VK_SHIFT);
82+
robot.keyPress(KeyEvent.VK_TAB);
83+
robot.keyRelease(KeyEvent.VK_TAB);
84+
robot.keyRelease(KeyEvent.VK_SHIFT);
85+
}
86+
87+
for(int i = 0; i < nx * ny - nx * ny / 2 - 1; i++) {
88+
robot.keyPress(KeyEvent.VK_LEFT);
89+
robot.keyRelease(KeyEvent.VK_LEFT);
90+
}
91+
92+
robot.keyPress(KeyEvent.VK_SHIFT);
93+
robot.keyPress(KeyEvent.VK_TAB);
94+
robot.keyRelease(KeyEvent.VK_TAB);
95+
robot.keyRelease(KeyEvent.VK_SHIFT);
96+
97+
robot.waitForIdle();
98+
robot.delay(200);
99+
100+
for(int i = 0; i < nx * ny; i++) {
101+
if(focusCnt[i] < 2) {
102+
SwingUtilities.invokeLater(window::dispose);
103+
throw new RuntimeException("Component " + i +
104+
" is not reachable in the backward focus cycle");
105+
} else if (focusCnt[i] > 2) {
106+
SwingUtilities.invokeLater(window::dispose);
107+
throw new RuntimeException("Component " + i +
108+
" got focus more than once in the backward focus cycle");
109+
}
110+
}
111+
112+
SwingUtilities.invokeLater(window::dispose);
113+
}
114+
115+
public static void initLayout(int nx, int ny)
116+
{
117+
window = new JFrame("Test");
118+
window.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
119+
JPanel rootPanel = new JPanel();
120+
rootPanel.setLayout(new BorderLayout());
121+
JPanel formPanel = new JPanel(new GridLayout(nx, ny));
122+
formPanel.setFocusTraversalPolicy(new LayoutFocusTraversalPolicy());
123+
formPanel.setFocusCycleRoot(true);
124+
ButtonGroup radioButtonGroup = new ButtonGroup();
125+
for(int i = 0; i < nx * ny; i++) {
126+
JToggleButton comp;
127+
if(i % 2 == 0) {
128+
comp = new JRadioButton("Grouped component");
129+
radioButtonGroup.add(comp);
130+
} else {
131+
comp = new JRadioButton("Single component");
132+
}
133+
formPanel.add(comp);
134+
int fi = i;
135+
comp.setBackground(Color.red);
136+
comp.addFocusListener(new FocusAdapter() {
137+
@Override
138+
public void focusGained(FocusEvent e) {
139+
focusCnt[fi]++;
140+
if( focusCnt[fi] == 1) {
141+
((JComponent) e.getSource())
142+
.setBackground(Color.yellow);
143+
} else if(focusCnt[fi] == 2) {
144+
((JComponent) e.getSource())
145+
.setBackground(Color.green);
146+
} else {
147+
((JComponent) e.getSource())
148+
.setBackground(Color.red);
149+
}
150+
}
151+
});
152+
}
153+
rootPanel.add(formPanel, BorderLayout.CENTER);
154+
window.add(rootPanel);
155+
window.pack();
156+
window.setVisible(true);
157+
}
158+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright (c) 2017, 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 8182577
26+
* @summary Verifies if moving focus via custom ButtonModel causes crash
27+
* @run main DefaultButtonModelCrashTest
28+
*/
29+
import java.awt.BorderLayout;
30+
import java.awt.Container;
31+
import java.awt.Point;
32+
import java.awt.Robot;
33+
import java.awt.event.KeyEvent;
34+
import javax.swing.ButtonModel;
35+
import javax.swing.DefaultButtonModel;
36+
import javax.swing.JCheckBox;
37+
import javax.swing.JComponent;
38+
import javax.swing.JFrame;
39+
import javax.swing.JPanel;
40+
import javax.swing.JTextField;
41+
import javax.swing.SwingUtilities;
42+
43+
public class DefaultButtonModelCrashTest {
44+
private JFrame frame = null;
45+
private JPanel panel;
46+
private volatile Point p = null;
47+
48+
public static void main(String[] args) throws Exception {
49+
new DefaultButtonModelCrashTest();
50+
}
51+
52+
public DefaultButtonModelCrashTest() throws Exception {
53+
try {
54+
Robot robot = new Robot();
55+
robot.setAutoDelay(200);
56+
SwingUtilities.invokeAndWait(() -> go());
57+
robot.waitForIdle();
58+
robot.keyPress(KeyEvent.VK_TAB);
59+
robot.keyRelease(KeyEvent.VK_TAB);
60+
robot.delay(100);
61+
robot.keyPress(KeyEvent.VK_TAB);
62+
robot.keyRelease(KeyEvent.VK_TAB);
63+
} finally {
64+
SwingUtilities.invokeAndWait(()->frame .dispose());
65+
}
66+
}
67+
68+
private void go() {
69+
70+
frame = new JFrame();
71+
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
72+
Container contentPane = frame.getContentPane();
73+
ButtonModel model = new DefaultButtonModel();
74+
75+
JCheckBox check = new JCheckBox("a bit broken");
76+
check.setModel(model);
77+
panel = new JPanel(new BorderLayout());
78+
panel.add(new JTextField("Press Tab (twice?)"), BorderLayout.NORTH);
79+
panel.add(check);
80+
contentPane.add(panel);
81+
frame.setLocationRelativeTo(null);
82+
frame.pack();
83+
frame.setVisible(true);
84+
}
85+
}

0 commit comments

Comments
 (0)