diff options
author | van <none@none> | 2014-10-10 13:14:29 -0700 |
---|---|---|
committer | van <none@none> | 2014-10-10 13:14:29 -0700 |
commit | 23d91d7b351500d256cd36924a3eeb6e29de1617 (patch) | |
tree | ae884231b6040a72e50194df8178109e51ad73e3 /src/share/classes/javax | |
parent | d8482cbb155d7a5cdd130738cae8b4b73448952f (diff) |
8033699: Incorrect radio button behavior
Reviewed-by: azvegint, alexsch
Diffstat (limited to 'src/share/classes/javax')
-rw-r--r-- | src/share/classes/javax/swing/plaf/basic/BasicRadioButtonUI.java | 357 |
1 files changed, 352 insertions, 5 deletions
diff --git a/src/share/classes/javax/swing/plaf/basic/BasicRadioButtonUI.java b/src/share/classes/javax/swing/plaf/basic/BasicRadioButtonUI.java index 47c9b7d75..89066d206 100644 --- a/src/share/classes/javax/swing/plaf/basic/BasicRadioButtonUI.java +++ b/src/share/classes/javax/swing/plaf/basic/BasicRadioButtonUI.java @@ -1,5 +1,5 @@ /* - * 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) { + } + } } |