--- a/jdk/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicRadioButtonUI.java Fri Oct 10 09:03:28 2014 -0700
+++ b/jdk/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicRadioButtonUI.java Fri Oct 10 09:09:06 2014 -0700
@@ -33,7 +33,8 @@
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
@@ -53,6 +54,8 @@
private final static String propertyPrefix = "RadioButton" + ".";
+ private KeyListener keyListener = null;
+
// ********************************
// Create PLAF
// ********************************
@@ -74,6 +77,7 @@
return radioButtonUI;
}
+ @Override
protected String getPropertyPrefix() {
return propertyPrefix;
}
@@ -81,7 +85,8 @@
// ********************************
// Install PLAF
// ********************************
- protected void installDefaults(AbstractButton b){
+ @Override
+ protected void installDefaults(AbstractButton b) {
super.installDefaults(b);
if(!defaults_initialized) {
icon = UIManager.getIcon(getPropertyPrefix() + "icon");
@@ -92,7 +97,8 @@
// ********************************
// Uninstall PLAF
// ********************************
- protected void uninstallDefaults(AbstractButton b){
+ @Override
+ protected void uninstallDefaults(AbstractButton b) {
super.uninstallDefaults(b);
defaults_initialized = false;
}
@@ -106,6 +112,65 @@
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
@@ -121,6 +186,7 @@
/**
* paint the radio button
*/
+ @Override
public synchronized void paint(Graphics g, JComponent c) {
AbstractButton b = (AbstractButton) c;
ButtonModel model = b.getModel();
@@ -217,7 +283,7 @@
* @param textRect bounds
* @param size the size of radio button
*/
- protected void paintFocus(Graphics g, Rectangle textRect, Dimension size){
+ protected void paintFocus(Graphics g, Rectangle textRect, Dimension size) {
}
@@ -235,6 +301,7 @@
/**
* The preferred size of the radio button
*/
+ @Override
public Dimension getPreferredSize(JComponent c) {
if(c.getComponentCount() > 0) {
return null;
@@ -280,4 +347,262 @@
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) {
+ }
+ }
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/swing/JRadioButton/8033699/bug8033699.java Fri Oct 10 09:09:06 2014 -0700
@@ -0,0 +1,255 @@
+/*
+ * 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();
+ }
+}