Skip to content

Commit

Permalink
8249548: backward focus traversal gets stuck in button group
Browse files Browse the repository at this point in the history
Backport-of: 22bb597
  • Loading branch information
zhengyu123 committed Aug 24, 2021
1 parent da90fbb commit 5a539db
Show file tree
Hide file tree
Showing 5 changed files with 539 additions and 619 deletions.
290 changes: 0 additions & 290 deletions src/java.desktop/macosx/classes/com/apple/laf/AquaButtonRadioUI.java
Expand Up @@ -56,31 +56,6 @@
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>() {
Expand Down Expand Up @@ -115,269 +90,4 @@ public RadioButtonBorder(final RadioButtonBorder other) {
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) {}
}
}
32 changes: 20 additions & 12 deletions src/java.desktop/macosx/classes/com/apple/laf/AquaButtonUI.java
Expand Up @@ -185,17 +185,13 @@ protected boolean setButtonType(final AbstractButton b, final Object prop) {
}

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);
Expand All @@ -221,15 +217,10 @@ protected void uninstallKeyboardActions(final AbstractButton b) {
}

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);
Expand All @@ -246,6 +237,23 @@ protected AquaButtonListener createButtonListener(final AbstractButton b) {
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;
Expand Down

1 comment on commit 5a539db

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.