8249548: backward focus traversal gets stuck in button group

Reviewed-by: serb
This commit is contained in:
Pankaj Bansal 2020-08-31 15:58:57 +05:30
parent 3d7b524593
commit 22bb597d0d
5 changed files with 539 additions and 619 deletions

View File

@ -56,31 +56,6 @@ import java.util.Set;
import java.util.Enumeration;
public class AquaButtonRadioUI extends AquaButtonLabeledUI {
private KeyListener keyListener = null;
@SuppressWarnings("serial")
private class SelectPreviousBtn extends AbstractAction {
public SelectPreviousBtn() {
super("Previous");
}
@Override
public void actionPerformed(ActionEvent e) {
AquaButtonRadioUI.this.selectRadioButton(e, false);
}
}
@SuppressWarnings("serial")
private class SelectNextBtn extends AbstractAction {
public SelectNextBtn() {
super("Next");
}
@Override
public void actionPerformed(ActionEvent e) {
AquaButtonRadioUI.this.selectRadioButton(e, true);
}
}
private static final RecyclableSingleton<AquaButtonRadioUI> instance = new RecyclableSingletonFromDefaultConstructor<AquaButtonRadioUI>(AquaButtonRadioUI.class);
private static final RecyclableSingleton<ImageIcon> sizingIcon = new RecyclableSingleton<ImageIcon>() {
@ -115,269 +90,4 @@ public class AquaButtonRadioUI extends AquaButtonLabeledUI {
super(other);
}
}
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());
}
@Override
protected void installListeners(AbstractButton button) {
super.installListeners(button);
//Only for JRadioButton
if (!(button instanceof JRadioButton))
return;
keyListener = createKeyListener();
button.addKeyListener(keyListener);
button.setFocusTraversalKeysEnabled(false);
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");
}
@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;
}
}
/**
* 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) {
Object eventSrc = event.getSource();
//Check whether the source is JRadioButton, if so, whether it is visible
if (!isValidRadioButtonObj(eventSrc))
return;
ButtonGroupInfo btnGroupInfo = new ButtonGroupInfo((JRadioButton)eventSrc);
btnGroupInfo.selectNewButton(next);
}
/**
* 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) {
return firstBtn;
}
boolean getButtonGroupInfo() {
if (activeBtn == null)
return false;
btnsInGroup.clear();
//Get the button model from ths 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;
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 compoennt 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;
}
//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 focus traversal key event
// on a radio button, consume the event if so and move the focus
// to next/previous component
@Override
public void keyPressed(KeyEvent e) {
AWTKeyStroke stroke = AWTKeyStroke.getAWTKeyStrokeForEvent(e);
if (stroke != null && e.getSource() instanceof JRadioButton) {
JRadioButton source = (JRadioButton) e.getSource();
boolean next = isFocusTraversalKey(source,
KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, stroke);
if (next || isFocusTraversalKey(source,
KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, stroke)) {
e.consume();
ButtonGroupInfo btnGroupInfo = new ButtonGroupInfo(source);
btnGroupInfo.jumpToNextComponent(next);
}
}
}
private boolean isFocusTraversalKey(JComponent c, int id,
AWTKeyStroke stroke) {
Set<AWTKeyStroke> keys = c.getFocusTraversalKeys(id);
return keys != null && keys.contains(stroke);
}
@Override public void keyReleased(KeyEvent e) {}
@Override public void keyTyped(KeyEvent e) {}
}
}

View File

@ -185,17 +185,13 @@ public class AquaButtonUI extends BasicButtonUI implements Sizeable {
}
protected void installListeners(final AbstractButton b) {
final AquaButtonListener listener = createButtonListener(b);
super.installListeners(b);
AquaButtonListener listener = getAquaButtonListener(b);
if (listener != null) {
// put the listener in the button's client properties so that
// we can get at it later
b.putClientProperty(this, listener);
b.addMouseListener(listener);
b.addMouseMotionListener(listener);
b.addFocusListener(listener);
b.addPropertyChangeListener(listener);
b.addChangeListener(listener);
b.addAncestorListener(listener);
}
installHierListener(b);
@ -221,15 +217,10 @@ public class AquaButtonUI extends BasicButtonUI implements Sizeable {
}
protected void uninstallListeners(final AbstractButton b) {
super.uninstallListeners(b);
final AquaButtonListener listener = (AquaButtonListener)b.getClientProperty(this);
b.putClientProperty(this, null);
if (listener != null) {
b.removeMouseListener(listener);
b.removeMouseListener(listener);
b.removeMouseMotionListener(listener);
b.removeFocusListener(listener);
b.removeChangeListener(listener);
b.removePropertyChangeListener(listener);
b.removeAncestorListener(listener);
}
uninstallHierListener(b);
@ -246,6 +237,23 @@ public class AquaButtonUI extends BasicButtonUI implements Sizeable {
return new AquaButtonListener(b);
}
/**
* Returns the AquaButtonListener for the passed in Button, or null if one
* could not be found.
*/
private AquaButtonListener getAquaButtonListener(AbstractButton b) {
MouseMotionListener[] listeners = b.getMouseMotionListeners();
if (listeners != null) {
for (MouseMotionListener listener : listeners) {
if (listener instanceof AquaButtonListener) {
return (AquaButtonListener) listener;
}
}
}
return null;
}
// Paint Methods
public void paint(final Graphics g, final JComponent c) {
final AbstractButton b = (AbstractButton)c;

View File

@ -35,6 +35,9 @@ import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import javax.swing.plaf.ButtonUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.ComponentUI;
@ -68,6 +71,8 @@ public class BasicButtonUI extends ButtonUI{
private static final Object BASIC_BUTTON_UI_KEY = new Object();
private KeyListener keyListener = null;
// ********************************
// Create PLAF
// ********************************
@ -156,6 +161,27 @@ public class BasicButtonUI extends ButtonUI{
b.addPropertyChangeListener(listener);
b.addChangeListener(listener);
}
if (b instanceof JToggleButton) {
keyListener = createKeyListener();
b.addKeyListener(keyListener);
// Need to get traversal key event
b.setFocusTraversalKeysEnabled(false);
// Map actions to the arrow keys
b.getActionMap().put("Previous", new BasicButtonUI.SelectPreviousBtn());
b.getActionMap().put("Next", new BasicButtonUI.SelectNextBtn());
b.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).
put(KeyStroke.getKeyStroke("UP"), "Previous");
b.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).
put(KeyStroke.getKeyStroke("DOWN"), "Next");
b.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).
put(KeyStroke.getKeyStroke("LEFT"), "Previous");
b.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).
put(KeyStroke.getKeyStroke("RIGHT"), "Next");
}
}
/**
@ -208,6 +234,24 @@ public class BasicButtonUI extends ButtonUI{
b.removeChangeListener(listener);
b.removePropertyChangeListener(listener);
}
if (b instanceof JToggleButton) {
// Unmap actions from the arrow keys
b.getActionMap().remove("Previous");
b.getActionMap().remove("Next");
b.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
.remove(KeyStroke.getKeyStroke("UP"));
b.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
.remove(KeyStroke.getKeyStroke("DOWN"));
b.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
.remove(KeyStroke.getKeyStroke("LEFT"));
b.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
.remove(KeyStroke.getKeyStroke("RIGHT"));
if (keyListener != null) {
b.removeKeyListener(keyListener);
keyListener = null;
}
}
}
/**
@ -566,4 +610,262 @@ public class BasicButtonUI extends ButtonUI{
return null;
}
/////////////////////////// Private functions ////////////////////////
/**
* Creates the key listener to handle tab navigation in JToggleButton Group.
*/
private KeyListener createKeyListener() {
if (keyListener == null) {
keyListener = new BasicButtonUI.KeyHandler();
}
return keyListener;
}
private boolean isValidToggleButtonObj(Object obj) {
return ((obj instanceof JToggleButton) &&
((JToggleButton) obj).isVisible() &&
((JToggleButton) obj).isEnabled());
}
/**
* Select toggle button based on "Previous" or "Next" operation
*
* @param event, the event object.
* @param next, indicate if it's next one
*/
private void selectToggleButton(ActionEvent event, boolean next) {
// Get the source of the event.
Object eventSrc = event.getSource();
// Check whether the source is JToggleButton, it so, whether it is visible
if (!isValidToggleButtonObj(eventSrc))
return;
BasicButtonUI.ButtonGroupInfo btnGroupInfo = new BasicButtonUI.ButtonGroupInfo((JToggleButton)eventSrc);
btnGroupInfo.selectNewButton(next);
}
/////////////////////////// Inner Classes ////////////////////////
@SuppressWarnings("serial")
private class SelectPreviousBtn extends AbstractAction {
public SelectPreviousBtn() {
super("Previous");
}
public void actionPerformed(ActionEvent e) {
BasicButtonUI.this.selectToggleButton(e, false);
}
}
@SuppressWarnings("serial")
private class SelectNextBtn extends AbstractAction{
public SelectNextBtn() {
super("Next");
}
public void actionPerformed(ActionEvent e) {
BasicButtonUI.this.selectToggleButton(e, true);
}
}
/**
* ButtonGroupInfo, used to get related info in button group
* for given toggle button
*/
private class ButtonGroupInfo {
JToggleButton activeBtn = null;
JToggleButton firstBtn = null;
JToggleButton lastBtn = null;
JToggleButton previousBtn = null;
JToggleButton nextBtn = null;
HashSet<JToggleButton> btnsInGroup = null;
boolean srcFound = false;
public ButtonGroupInfo(JToggleButton btn) {
activeBtn = btn;
btnsInGroup = new HashSet<JToggleButton>();
}
// 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){
return firstBtn;
}
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 (!isValidToggleButtonObj(curElement))
continue;
btnsInGroup.add((JToggleButton) curElement);
// If firstBtn is not set yet, curElement is that first button
if (null == firstBtn)
firstBtn = (JToggleButton) 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 = (JToggleButton) curElement;
} else if (nextBtn == null) {
// The source has been found and the current element
// is the next valid button of the list
nextBtn = (JToggleButton) curElement;
}
// Set new last "valid" JToggleButton of the list
lastBtn = (JToggleButton) curElement;
}
return true;
}
/**
* Find the new toggle 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) {
JToggleButton 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)) {
ButtonModel btnModel = newSelectedBtn.getModel();
btnModel.setPressed(true);
btnModel.setArmed(true);
newSelectedBtn.requestFocusInWindow();
newSelectedBtn.setSelected(true);
btnModel.setPressed(false);
btnModel.setArmed(false);
}
}
}
/**
* Find the button group the passed in JToggleButton 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);
}
}
}
}
/**
* Togglebutton KeyListener
*/
private class KeyHandler implements KeyListener {
// This listener checks if the key event is a focus traversal key event
// on a toggle button, consume the event if so and move the focus
// to next/previous component
public void keyPressed(KeyEvent e) {
AWTKeyStroke stroke = AWTKeyStroke.getAWTKeyStrokeForEvent(e);
if (stroke != null && e.getSource() instanceof JToggleButton) {
JToggleButton source = (JToggleButton) e.getSource();
boolean next = isFocusTraversalKey(source,
KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
stroke);
if (next || isFocusTraversalKey(source,
KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
stroke)) {
e.consume();
BasicButtonUI.ButtonGroupInfo btnGroupInfo = new BasicButtonUI.ButtonGroupInfo(source);
btnGroupInfo.jumpToNextComponent(next);
}
}
}
private boolean isFocusTraversalKey(JComponent c, int id,
AWTKeyStroke stroke) {
Set<AWTKeyStroke> keys = c.getFocusTraversalKeys(id);
return keys != null && keys.contains(stroke);
}
public void keyReleased(KeyEvent e) {
}
public void keyTyped(KeyEvent e) {
}
}
}

View File

@ -118,65 +118,6 @@ public class BasicRadioButtonUI extends BasicToggleButtonUI
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
@ -353,262 +294,4 @@ 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){
return firstBtn;
}
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)) {
ButtonModel btnModel = newSelectedBtn.getModel();
btnModel.setPressed(true);
btnModel.setArmed(true);
newSelectedBtn.requestFocusInWindow();
newSelectedBtn.setSelected(true);
btnModel.setPressed(false);
btnModel.setArmed(false);
}
}
}
/**
* 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 focus traversal key event
// on a radio button, consume the event if so and move the focus
// to next/previous component
public void keyPressed(KeyEvent e) {
AWTKeyStroke stroke = AWTKeyStroke.getAWTKeyStrokeForEvent(e);
if (stroke != null && e.getSource() instanceof JRadioButton) {
JRadioButton source = (JRadioButton) e.getSource();
boolean next = isFocusTraversalKey(source,
KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
stroke);
if (next || isFocusTraversalKey(source,
KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
stroke)) {
e.consume();
ButtonGroupInfo btnGroupInfo = new ButtonGroupInfo(source);
btnGroupInfo.jumpToNextComponent(next);
}
}
}
private boolean isFocusTraversalKey(JComponent c, int id,
AWTKeyStroke stroke) {
Set<AWTKeyStroke> keys = c.getFocusTraversalKeys(id);
return keys != null && keys.contains(stroke);
}
public void keyReleased(KeyEvent e) {
}
public void keyTyped(KeyEvent e) {
}
}
}

View File

@ -0,0 +1,217 @@
/*
* Copyright (c) 2020, 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
* @key headful
* @bug 8249548
* @summary Test focus traversal in button group containing JToggleButton
* and JRadioButton
* @run main TestButtonGroupFocusTraversal
*/
import javax.swing.ButtonGroup;
import javax.swing.JFrame;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import java.awt.Component;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.Robot;
import java.awt.event.KeyEvent;
public class TestButtonGroupFocusTraversal {
private static JFrame frame;
private static JTextField textFieldFirst, textFieldLast;
private static JToggleButton toggleButton1, toggleButton2;
private static JRadioButton radioButton1, radioButton2;
private static Robot robot;
private static void blockTillDisplayed(Component comp) {
Point p = null;
while (p == null) {
try {
p = comp.getLocationOnScreen();
} catch (IllegalStateException e) {
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
}
private static void createUI() throws Exception {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
textFieldFirst = new JTextField("First");
textFieldLast = new JTextField("Last");
toggleButton1 = new JToggleButton("1");
toggleButton2 = new JToggleButton("2");
radioButton1 = new JRadioButton("1");
radioButton2 = new JRadioButton("2");
ButtonGroup toggleGroup = new ButtonGroup();
toggleGroup.add(toggleButton1);
toggleGroup.add(toggleButton2);
ButtonGroup radioGroup = new ButtonGroup();
radioGroup.add(radioButton1);
radioGroup.add(radioButton2);
toggleButton2.setSelected(true);
radioButton2.setSelected(true);
frame = new JFrame("Test");
frame.setLayout(new FlowLayout());
Container pane = frame.getContentPane();
pane.add(textFieldFirst);
pane.add(toggleButton1);
pane.add(toggleButton2);
pane.add(radioButton1);
pane.add(radioButton2);
pane.add(textFieldLast);
frame.pack();
frame.setAlwaysOnTop(true);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
});
}
private static void pressKey(int ...keys) {
int num = keys.length;
for (int i=0; i<num; i++)
robot.keyPress(keys[i]);
for (int i=num; i>0; i--)
robot.keyRelease(keys[i-1]);
robot.waitForIdle();
robot.delay(200);
}
private static void checkFocusedComponent (Component component) {
Component focusedComponent = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
if (!focusedComponent.equals(component)) {
System.out.println(component);
System.out.println(focusedComponent);
throw new RuntimeException("Wrong Component Selected");
}
}
public static void main(String[] args) throws Exception {
robot = new Robot();
robot.setAutoDelay(100);
UIManager.LookAndFeelInfo infos[] = UIManager.getInstalledLookAndFeels();
for (UIManager.LookAndFeelInfo info : infos) {
UIManager.setLookAndFeel(info.getClassName());
System.out.println(info.getClassName());
try {
createUI();
robot.waitForIdle();
robot.delay(200);
blockTillDisplayed(frame);
SwingUtilities.invokeAndWait(textFieldFirst::requestFocus);
if (!textFieldFirst.equals(KeyboardFocusManager.getCurrentKeyboardFocusManager()
.getFocusOwner())) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
SwingUtilities.invokeAndWait(textFieldFirst::requestFocus);
}
robot.waitForIdle();
robot.delay(200);
pressKey(KeyEvent.VK_TAB);
checkFocusedComponent(toggleButton2);
pressKey(KeyEvent.VK_TAB);
checkFocusedComponent(radioButton2);
pressKey(KeyEvent.VK_TAB);
checkFocusedComponent(textFieldLast);
pressKey(KeyEvent.VK_SHIFT, KeyEvent.VK_TAB);
checkFocusedComponent(radioButton2);
pressKey(KeyEvent.VK_SHIFT, KeyEvent.VK_TAB);
checkFocusedComponent(toggleButton2);
pressKey(KeyEvent.VK_SHIFT, KeyEvent.VK_TAB);
checkFocusedComponent(textFieldFirst);
pressKey(KeyEvent.VK_TAB);
checkFocusedComponent(toggleButton2);
pressKey(KeyEvent.VK_LEFT);
checkFocusedComponent(toggleButton1);
pressKey(KeyEvent.VK_RIGHT);
checkFocusedComponent(toggleButton2);
pressKey(KeyEvent.VK_UP);
checkFocusedComponent(toggleButton1);
pressKey(KeyEvent.VK_DOWN);
checkFocusedComponent(toggleButton2);
pressKey(KeyEvent.VK_TAB);
checkFocusedComponent(radioButton2);
pressKey(KeyEvent.VK_LEFT);
checkFocusedComponent(radioButton1);
pressKey(KeyEvent.VK_RIGHT);
checkFocusedComponent(radioButton2);
pressKey(KeyEvent.VK_UP);
checkFocusedComponent(radioButton1);
pressKey(KeyEvent.VK_DOWN);
checkFocusedComponent(radioButton2);
} finally {
if (frame != null) {
SwingUtilities.invokeAndWait(frame::dispose);
}
}
}
}
}