--- old/src/java.desktop/macosx/classes/com/apple/laf/AquaButtonRadioUI.java 2018-08-11 18:47:05.000000000 +0530 +++ new/src/java.desktop/macosx/classes/com/apple/laf/AquaButtonRadioUI.java 2018-08-11 18:47:04.000000000 +0530 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2018, 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 @@ -25,15 +25,63 @@ package com.apple.laf; -import javax.swing.*; +import javax.swing.JComponent; +import javax.swing.ImageIcon; +import javax.swing.JRadioButton; +import javax.swing.Icon; +import javax.swing.AbstractButton; +import javax.swing.AbstractAction; +import javax.swing.KeyStroke; +import javax.swing.DefaultButtonModel; +import javax.swing.ButtonGroup; +import javax.swing.ButtonModel; import javax.swing.plaf.ComponentUI; -import apple.laf.JRSUIConstants.*; - -import com.apple.laf.AquaUtilControlSize.*; -import com.apple.laf.AquaUtils.*; +import java.awt.Component; +import java.awt.AWTKeyStroke; +import java.awt.KeyboardFocusManager; + +import java.awt.event.ActionEvent; +import java.awt.event.KeyListener; +import java.awt.event.KeyEvent; + +import apple.laf.JRSUIConstants.Widget; +import com.apple.laf.AquaUtilControlSize.SizeVariant; +import com.apple.laf.AquaUtilControlSize.SizeDescriptor; +import com.apple.laf.AquaUtils.RecyclableSingleton; +import com.apple.laf.AquaUtils.RecyclableSingletonFromDefaultConstructor; + +import java.util.HashSet; +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 instance = new RecyclableSingletonFromDefaultConstructor(AquaButtonRadioUI.class); private static final RecyclableSingleton sizingIcon = new RecyclableSingleton() { protected ImageIcon getInstance() { @@ -67,4 +115,268 @@ 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 btnsInGroup = null; + boolean srcFound = false; + + public ButtonGroupInfo(JRadioButton btn) { + activeBtn = btn; + btnsInGroup = new HashSet(); + } + + //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 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 keys = c.getFocusTraversalKeys(id); + return keys != null && keys.contains(stroke); + } + + @Override public void keyReleased(KeyEvent e) {} + + @Override public void keyTyped(KeyEvent e) {} + } } --- old/test/jdk/javax/swing/JRadioButton/8033699/bug8033699.java 2018-08-11 18:47:06.000000000 +0530 +++ new/test/jdk/javax/swing/JRadioButton/8033699/bug8033699.java 2018-08-11 18:47:06.000000000 +0530 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2018, 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 @@ -26,7 +26,7 @@ * @key headful * @library ../../regtesthelpers * @build Util - * @bug 8033699 8154043 8167160 + * @bug 8033699 8154043 8167160 8208640 * @summary Incorrect radio button behavior when pressing tab key * @run main bug8033699 */ @@ -59,12 +59,9 @@ private static JRadioButton radioBtnSingle; public static void main(String args[]) throws Throwable { - SwingUtilities.invokeAndWait(new Runnable() { - @Override - public void run() { + SwingUtilities.invokeAndWait(() -> { changeLAF(); createAndShowGUI(); - } }); robot = new Robot(); @@ -96,19 +93,14 @@ // down key circle back to first button in grouped radio button runTest8(); - SwingUtilities.invokeAndWait(new Runnable() { - @Override - public void run() { - mainFrame.dispose(); - } - }); + SwingUtilities.invokeAndWait(() -> mainFrame.dispose()); } private static void changeLAF() { String currentLAF = UIManager.getLookAndFeel().toString(); System.out.println(currentLAF); currentLAF = currentLAF.toLowerCase(); - if (currentLAF.contains("aqua") || currentLAF.contains("nimbus")) { + if (currentLAF.contains("nimbus")) { try { UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel"); } catch (Exception ex) { @@ -167,13 +159,10 @@ hitKey(robot, KeyEvent.VK_TAB); hitKey(robot, KeyEvent.VK_TAB); - SwingUtilities.invokeAndWait(new Runnable() { - @Override - 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"); - } + SwingUtilities.invokeAndWait(() -> { + 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"); } }); } @@ -181,13 +170,10 @@ // 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() { - @Override - 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"); - } + SwingUtilities.invokeAndWait(() -> { + 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"); } }); } @@ -197,13 +183,10 @@ hitKey(robot, KeyEvent.VK_SHIFT, KeyEvent.VK_TAB); hitKey(robot, KeyEvent.VK_SHIFT, KeyEvent.VK_TAB); hitKey(robot, KeyEvent.VK_SHIFT, KeyEvent.VK_TAB); - SwingUtilities.invokeAndWait(new Runnable() { - @Override - public void run() { - if (KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() != radioBtn1) { - System.out.println("Radio button Group/Non Grouped Radio Button SHIFT-Tab Key Test failed"); - throw new RuntimeException("Focus is not on Radio Button A as Expected"); - } + SwingUtilities.invokeAndWait(() -> { + if (KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() != radioBtn1) { + System.out.println("Radio button Group/Non Grouped Radio Button SHIFT-Tab Key Test failed"); + throw new RuntimeException("Focus is not on Radio Button A as Expected"); } }); } @@ -212,13 +195,10 @@ private static void runTest4() throws Exception { hitKey(robot, KeyEvent.VK_DOWN); hitKey(robot, KeyEvent.VK_RIGHT); - SwingUtilities.invokeAndWait(new Runnable() { - @Override - public void run() { - if (KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() != radioBtn3) { - System.out.println("Radio button Group UP/LEFT Arrow Key Move Focus Failed"); - throw new RuntimeException("Focus is not on Radio Button C as Expected"); - } + SwingUtilities.invokeAndWait(() -> { + if (KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() != radioBtn3) { + System.out.println("Radio button Group UP/LEFT Arrow Key Move Focus Failed"); + throw new RuntimeException("Focus is not on Radio Button C as Expected"); } }); } @@ -226,13 +206,10 @@ private static void runTest5() throws Exception { hitKey(robot, KeyEvent.VK_UP); hitKey(robot, KeyEvent.VK_LEFT); - SwingUtilities.invokeAndWait(new Runnable() { - @Override - public void run() { - if (KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() != radioBtn1) { - System.out.println("Radio button Group Left/Up Arrow Key Move Focus Failed"); - throw new RuntimeException("Focus is not on Radio Button A as Expected"); - } + SwingUtilities.invokeAndWait(() -> { + if (KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() != radioBtn1) { + System.out.println("Radio button Group Left/Up Arrow Key Move Focus Failed"); + throw new RuntimeException("Focus is not on Radio Button A as Expected"); } }); } @@ -240,39 +217,30 @@ private static void runTest6() throws Exception { hitKey(robot, KeyEvent.VK_UP); hitKey(robot, KeyEvent.VK_UP); - SwingUtilities.invokeAndWait(new Runnable() { - @Override - 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 B as Expected"); - } + SwingUtilities.invokeAndWait(() -> { + 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 B as Expected"); } }); } private static void runTest7() throws Exception { hitKey(robot, KeyEvent.VK_TAB); - SwingUtilities.invokeAndWait(new Runnable() { - @Override - 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"); - } + SwingUtilities.invokeAndWait(() -> { + 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() { - @Override - public void run() { - if (KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() != radioBtnSingle) { - System.out.println("Separate Component added in button group layout"); - throw new RuntimeException("Focus is not on Radio Button Single as Expected"); - } + SwingUtilities.invokeAndWait(() -> { + if (KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() != radioBtnSingle) { + System.out.println("Separate Component added in button group layout"); + throw new RuntimeException("Focus is not on Radio Button Single as Expected"); } }); }