提交 82442f7a 编写于 作者: V van

8033699: Incorrect radio button behavior

Reviewed-by: azvegint, alexsch
上级 cdb7be5f
/*
* Copyright (c) 1997, 2008, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2014, 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
......@@ -33,7 +33,8 @@ import javax.swing.plaf.*;
import javax.swing.text.View;
import sun.swing.SwingUtilities2;
import sun.awt.AppContext;
import java.util.Enumeration;
import java.util.HashSet;
/**
* RadioButtonUI implementation for BasicRadioButtonUI
......@@ -44,15 +45,27 @@ public class BasicRadioButtonUI extends BasicToggleButtonUI
{
private static final Object BASIC_RADIO_BUTTON_UI_KEY = new Object();
/**
* The icon.
*/
protected Icon icon;
private boolean defaults_initialized = false;
private final static String propertyPrefix = "RadioButton" + ".";
private KeyListener keyListener = null;
// ********************************
// Create PLAF
// ********************************
/**
* Returns an instance of {@code BasicRadioButtonUI}.
*
* @param b a component
* @return an instance of {@code BasicRadioButtonUI}
*/
public static ComponentUI createUI(JComponent b) {
AppContext appContext = AppContext.getAppContext();
BasicRadioButtonUI radioButtonUI =
......@@ -64,6 +77,7 @@ public class BasicRadioButtonUI extends BasicToggleButtonUI
return radioButtonUI;
}
@Override
protected String getPropertyPrefix() {
return propertyPrefix;
}
......@@ -71,7 +85,8 @@ public class BasicRadioButtonUI extends BasicToggleButtonUI
// ********************************
// Install PLAF
// ********************************
protected void installDefaults(AbstractButton b){
@Override
protected void installDefaults(AbstractButton b) {
super.installDefaults(b);
if(!defaults_initialized) {
icon = UIManager.getIcon(getPropertyPrefix() + "icon");
......@@ -82,15 +97,80 @@ public class BasicRadioButtonUI extends BasicToggleButtonUI
// ********************************
// Uninstall PLAF
// ********************************
protected void uninstallDefaults(AbstractButton b){
@Override
protected void uninstallDefaults(AbstractButton b) {
super.uninstallDefaults(b);
defaults_initialized = false;
}
/**
* Returns the default icon.
*
* @return the default icon
*/
public Icon getDefaultIcon() {
return icon;
}
// ********************************
// Install Listeners
// ********************************
@Override
protected void installListeners(AbstractButton button) {
super.installListeners(button);
// Only for JRadioButton
if (!(button instanceof JRadioButton))
return;
keyListener = createKeyListener();
button.addKeyListener(keyListener);
// Need to get traversal key event
button.setFocusTraversalKeysEnabled(false);
// Map actions to the arrow keys
button.getActionMap().put("Previous", new SelectPreviousBtn());
button.getActionMap().put("Next", new SelectNextBtn());
button.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).
put(KeyStroke.getKeyStroke("UP"), "Previous");
button.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).
put(KeyStroke.getKeyStroke("DOWN"), "Next");
button.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).
put(KeyStroke.getKeyStroke("LEFT"), "Previous");
button.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).
put(KeyStroke.getKeyStroke("RIGHT"), "Next");
}
// ********************************
// UnInstall Listeners
// ********************************
@Override
protected void uninstallListeners(AbstractButton button) {
super.uninstallListeners(button);
// Only for JRadioButton
if (!(button instanceof JRadioButton))
return;
// Unmap actions from the arrow keys
button.getActionMap().remove("Previous");
button.getActionMap().remove("Next");
button.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
.remove(KeyStroke.getKeyStroke("UP"));
button.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
.remove(KeyStroke.getKeyStroke("DOWN"));
button.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
.remove(KeyStroke.getKeyStroke("LEFT"));
button.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
.remove(KeyStroke.getKeyStroke("RIGHT"));
if (keyListener != null) {
button.removeKeyListener(keyListener);
keyListener = null;
}
}
/* These Dimensions/Rectangles are allocated once for all
* RadioButtonUI.paint() calls. Re-using rectangles
......@@ -106,6 +186,7 @@ public class BasicRadioButtonUI extends BasicToggleButtonUI
/**
* paint the radio button
*/
@Override
public synchronized void paint(Graphics g, JComponent c) {
AbstractButton b = (AbstractButton) c;
ButtonModel model = b.getModel();
......@@ -195,7 +276,14 @@ public class BasicRadioButtonUI extends BasicToggleButtonUI
}
}
protected void paintFocus(Graphics g, Rectangle textRect, Dimension size){
/**
* Paints focused radio button.
*
* @param g an instance of {@code Graphics}
* @param textRect bounds
* @param size the size of radio button
*/
protected void paintFocus(Graphics g, Rectangle textRect, Dimension size) {
}
......@@ -213,6 +301,7 @@ public class BasicRadioButtonUI extends BasicToggleButtonUI
/**
* The preferred size of the radio button
*/
@Override
public Dimension getPreferredSize(JComponent c) {
if(c.getComponentCount() > 0) {
return null;
......@@ -258,4 +347,262 @@ public class BasicRadioButtonUI extends BasicToggleButtonUI
height += prefInsets.top + prefInsets.bottom;
return new Dimension(width, height);
}
/////////////////////////// Private functions ////////////////////////
/**
* Creates the key listener to handle tab navigation in JRadioButton Group.
*/
private KeyListener createKeyListener() {
if (keyListener == null) {
keyListener = new KeyHandler();
}
return keyListener;
}
private boolean isValidRadioButtonObj(Object obj) {
return ((obj instanceof JRadioButton) &&
((JRadioButton) obj).isVisible() &&
((JRadioButton) obj).isEnabled());
}
/**
* Select radio button based on "Previous" or "Next" operation
*
* @param event, the event object.
* @param next, indicate if it's next one
*/
private void selectRadioButton(ActionEvent event, boolean next) {
// Get the source of the event.
Object eventSrc = event.getSource();
// Check whether the source is JRadioButton, it so, whether it is visible
if (!isValidRadioButtonObj(eventSrc))
return;
ButtonGroupInfo btnGroupInfo = new ButtonGroupInfo((JRadioButton)eventSrc);
btnGroupInfo.selectNewButton(next);
}
/////////////////////////// Inner Classes ////////////////////////
@SuppressWarnings("serial")
private class SelectPreviousBtn extends AbstractAction {
public SelectPreviousBtn() {
super("Previous");
}
public void actionPerformed(ActionEvent e) {
BasicRadioButtonUI.this.selectRadioButton(e, false);
}
}
@SuppressWarnings("serial")
private class SelectNextBtn extends AbstractAction{
public SelectNextBtn() {
super("Next");
}
public void actionPerformed(ActionEvent e) {
BasicRadioButtonUI.this.selectRadioButton(e, true);
}
}
/**
* ButtonGroupInfo, used to get related info in button group
* for given radio button
*/
private class ButtonGroupInfo {
JRadioButton activeBtn = null;
JRadioButton firstBtn = null;
JRadioButton lastBtn = null;
JRadioButton previousBtn = null;
JRadioButton nextBtn = null;
HashSet<JRadioButton> btnsInGroup = null;
boolean srcFound = false;
public ButtonGroupInfo(JRadioButton btn) {
activeBtn = btn;
btnsInGroup = new HashSet<JRadioButton>();
}
// Check if given object is in the button group
boolean containsInGroup(Object obj){
return btnsInGroup.contains(obj);
}
// Check if the next object to gain focus belongs
// to the button group or not
Component getFocusTransferBaseComponent(boolean next){
Component focusBaseComp = activeBtn;
Window container = SwingUtilities.getWindowAncestor(activeBtn);
if (container != null) {
FocusTraversalPolicy policy = container.getFocusTraversalPolicy();
Component comp = next ? policy.getComponentAfter(container, activeBtn)
: policy.getComponentBefore(container, activeBtn);
// If next component in the button group, use last/first button as base focus
// otherwise, use the activeBtn as the base focus
if (containsInGroup(comp)) {
focusBaseComp = next ? lastBtn : firstBtn;
}
}
return focusBaseComp;
}
boolean getButtonGroupInfo() {
if (activeBtn == null)
return false;
btnsInGroup.clear();
// Get the button model from the source.
ButtonModel model = activeBtn.getModel();
if (!(model instanceof DefaultButtonModel))
return false;
// If the button model is DefaultButtonModel, and use it, otherwise return.
DefaultButtonModel bm = (DefaultButtonModel) model;
// get the ButtonGroup of the button from the button model
ButtonGroup group = bm.getGroup();
if (group == null)
return false;
// Get all the buttons in the group
Enumeration<AbstractButton> e = group.getElements();
if (e == null)
return false;
while (e.hasMoreElements()) {
AbstractButton curElement = e.nextElement();
if (!isValidRadioButtonObj(curElement))
continue;
btnsInGroup.add((JRadioButton) curElement);
// If firstBtn is not set yet, curElement is that first button
if (null == firstBtn)
firstBtn = (JRadioButton) curElement;
if (activeBtn == curElement)
srcFound = true;
else if (!srcFound) {
// The source has not been yet found and the current element
// is the last previousBtn
previousBtn = (JRadioButton) curElement;
} else if (nextBtn == null) {
// The source has been found and the current element
// is the next valid button of the list
nextBtn = (JRadioButton) curElement;
}
// Set new last "valid" JRadioButton of the list
lastBtn = (JRadioButton) curElement;
}
return true;
}
/**
* Find the new radio button that focus needs to be
* moved to in the group, select the button
*
* @param next, indicate if it's arrow up/left or down/right
*/
void selectNewButton(boolean next) {
if (!getButtonGroupInfo())
return;
if (srcFound) {
JRadioButton newSelectedBtn = null;
if (next) {
// Select Next button. Cycle to the first button if the source
// button is the last of the group.
newSelectedBtn = (null == nextBtn) ? firstBtn : nextBtn;
} else {
// Select previous button. Cycle to the last button if the source
// button is the first button of the group.
newSelectedBtn = (null == previousBtn) ? lastBtn : previousBtn;
}
if (newSelectedBtn != null &&
(newSelectedBtn != activeBtn)) {
newSelectedBtn.requestFocusInWindow();
newSelectedBtn.setSelected(true);
}
}
}
/**
* Find the button group the passed in JRadioButton belongs to, and
* move focus to next component of the last button in the group
* or previous component of first button
*
* @param next, indicate if jump to next component or previous
*/
void jumpToNextComponent(boolean next) {
if (!getButtonGroupInfo()){
// In case the button does not belong to any group, it needs
// to be treated as a component
if (activeBtn != null){
lastBtn = activeBtn;
firstBtn = activeBtn;
}
else
return;
}
// Update the component we will use as base to transfer
// focus from
JComponent compTransferFocusFrom = activeBtn;
// If next component in the parent window is not in
// the button group, current active button will be
// base, otherwise, the base will be first or last
// button in the button group
Component focusBase = getFocusTransferBaseComponent(next);
if (focusBase != null){
if (next) {
KeyboardFocusManager.
getCurrentKeyboardFocusManager().focusNextComponent(focusBase);
} else {
KeyboardFocusManager.
getCurrentKeyboardFocusManager().focusPreviousComponent(focusBase);
}
}
}
}
/**
* Radiobutton KeyListener
*/
private class KeyHandler implements KeyListener {
// This listener checks if the key event is a KeyEvent.VK_TAB
// or shift + KeyEvent.VK_TAB event on a radio button, consume the event
// if so and move the focus to next/previous component
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_TAB) {
// Get the source of the event.
Object eventSrc = e.getSource();
// Check whether the source is a visible and enabled JRadioButton
if (isValidRadioButtonObj(eventSrc)) {
e.consume();
ButtonGroupInfo btnGroupInfo = new ButtonGroupInfo((JRadioButton)eventSrc);
btnGroupInfo.jumpToNextComponent(!e.isShiftDown());
}
}
}
public void keyReleased(KeyEvent e) {
}
public void keyTyped(KeyEvent e) {
}
}
}
/*
* Copyright (c) 2014, 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
* @library ../../regtesthelpers
* @build Util
* @bug 8033699
* @summary Incorrect radio button behavior when pressing tab key
* @author Vivi An
* @run main bug8033699
*/
import javax.swing.*;
import javax.swing.event.*;
import java.awt.event.*;
import java.awt.*;
import sun.awt.SunToolkit;
public class bug8033699 {
private static Robot robot;
private static SunToolkit toolkit;
private static JButton btnStart;
private static ButtonGroup btnGrp;
private static JButton btnEnd;
private static JButton btnMiddle;
private static JRadioButton radioBtn1;
private static JRadioButton radioBtn2;
private static JRadioButton radioBtn3;
private static JRadioButton radioBtnSingle;
public static void main(String args[]) throws Throwable {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
createAndShowGUI();
}
});
robot = new Robot();
Thread.sleep(100);
robot.setAutoDelay(100);
toolkit = (SunToolkit) Toolkit.getDefaultToolkit();
// tab key test grouped radio button
runTest1();
// tab key test non-grouped radio button
runTest2();
// shift tab key test grouped and non grouped radio button
runTest3();
// left/up key test in grouped radio button
runTest4();
// down/right key test in grouped radio button
runTest5();
// tab from radio button in group to next component in the middle of button group layout
runTest6();
// tab to radio button in group from component in the middle of button group layout
runTest7();
// down key circle back to first button in grouped radio button
runTest8();
}
private static void createAndShowGUI() {
JFrame mainFrame = new JFrame("Bug 8033699 - 8 Tests for Grouped/Non Group Radio Buttons");
btnStart = new JButton("Start");
btnEnd = new JButton("End");
btnMiddle = new JButton("Middle");
JPanel box = new JPanel();
box.setLayout(new BoxLayout(box, BoxLayout.Y_AXIS));
box.setBorder(BorderFactory.createTitledBorder("Grouped Radio Buttons"));
radioBtn1 = new JRadioButton("A");
radioBtn2 = new JRadioButton("B");
radioBtn3 = new JRadioButton("C");
ButtonGroup btnGrp = new ButtonGroup();
btnGrp.add(radioBtn1);
btnGrp.add(radioBtn2);
btnGrp.add(radioBtn3);
radioBtn1.setSelected(true);
box.add(radioBtn1);
box.add(radioBtn2);
box.add(btnMiddle);
box.add(radioBtn3);
radioBtnSingle = new JRadioButton("Not Grouped");
radioBtnSingle.setSelected(true);
mainFrame.getContentPane().add(btnStart);
mainFrame.getContentPane().add(box);
mainFrame.getContentPane().add(radioBtnSingle);
mainFrame.getContentPane().add(btnEnd);
mainFrame.getRootPane().setDefaultButton(btnStart);
btnStart.requestFocus();
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainFrame.setLayout(new BoxLayout(mainFrame.getContentPane(), BoxLayout.Y_AXIS));
mainFrame.setSize(300, 300);
mainFrame.setLocation(200, 200);
mainFrame.setVisible(true);
mainFrame.toFront();
}
// Radio button Group as a single component when traversing through tab key
private static void runTest1() throws Exception{
hitKey(robot, KeyEvent.VK_TAB);
hitKey(robot, KeyEvent.VK_TAB);
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
if (KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() != radioBtnSingle) {
System.out.println("Radio Button Group Go To Next Component through Tab Key failed");
throw new RuntimeException("Focus is not on Radio Button Single as Expected");
}
}
});
}
// Non-Grouped Radio button as a single component when traversing through tab key
private static void runTest2() throws Exception{
hitKey(robot, KeyEvent.VK_TAB);
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
if (KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() != btnEnd) {
System.out.println("Non Grouped Radio Button Go To Next Component through Tab Key failed");
throw new RuntimeException("Focus is not on Button End as Expected");
}
}
});
}
// Non-Grouped Radio button and Group Radio button as a single component when traversing through shift-tab key
private static void runTest3() throws Exception{
hitKey(robot, KeyEvent.VK_SHIFT, KeyEvent.VK_TAB);
hitKey(robot, KeyEvent.VK_SHIFT, KeyEvent.VK_TAB);
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
if (KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() != radioBtn3) {
System.out.println("Radio button Group/Non Grouped Radio Button SHIFT-Tab Key Test failed");
throw new RuntimeException("Focus is not on Radio Button C as Expected");
}
}
});
}
// Using arrow key to move focus in radio button group
private static void runTest4() throws Exception{
hitKey(robot, KeyEvent.VK_UP);
hitKey(robot, KeyEvent.VK_LEFT);
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
if (KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() != radioBtn1) {
System.out.println("Radio button Group UP/LEFT Arrow Key Move Focus Failed");
throw new RuntimeException("Focus is not on Radio Button A as Expected");
}
}
});
}
private static void runTest5() throws Exception{
hitKey(robot, KeyEvent.VK_DOWN);
hitKey(robot, KeyEvent.VK_RIGHT);
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
if (KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() != radioBtn3) {
System.out.println("Radio button Group Left/Up Arrow Key Move Focus Failed");
throw new RuntimeException("Focus is not on Radio Button C as Expected");
}
}
});
}
private static void runTest6() throws Exception{
hitKey(robot, KeyEvent.VK_DOWN);
hitKey(robot, KeyEvent.VK_DOWN);
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
if (KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() != radioBtn2) {
System.out.println("Radio button Group Circle Back To First Button Test");
throw new RuntimeException("Focus is not on Radio Button A as Expected");
}
}
});
}
private static void runTest7() throws Exception{
hitKey(robot, KeyEvent.VK_TAB);
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
if (KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() != btnMiddle) {
System.out.println("Separate Component added in button group layout");
throw new RuntimeException("Focus is not on Middle Button as Expected");
}
}
});
}
private static void runTest8() throws Exception{
hitKey(robot, KeyEvent.VK_TAB);
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
if (KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() != radioBtn3) {
System.out.println("Separate Component added in button group layout");
throw new RuntimeException("Focus is not on Radio Button C as Expected");
}
}
});
}
private static void hitKey(Robot robot, int keycode) {
robot.keyPress(keycode);
robot.keyRelease(keycode);
toolkit.realSync();
}
private static void hitKey(Robot robot, int mode, int keycode) {
robot.keyPress(mode);
robot.keyPress(keycode);
robot.keyRelease(mode);
robot.keyRelease(keycode);
toolkit.realSync();
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册