blob: 03ed97d1a4ff8f83380e190b3753aa5074e2fa82 [file] [log] [blame]
/*
* Copyright (c) 1997, 2016, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.
*/
package javax.swing.plaf.basic;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
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;
import java.util.Set;
/**
* RadioButtonUI implementation for BasicRadioButtonUI
*
* @author Jeff Dinkins
*/
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 static final 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 =
(BasicRadioButtonUI) appContext.get(BASIC_RADIO_BUTTON_UI_KEY);
if (radioButtonUI == null) {
radioButtonUI = new BasicRadioButtonUI();
appContext.put(BASIC_RADIO_BUTTON_UI_KEY, radioButtonUI);
}
return radioButtonUI;
}
@Override
protected String getPropertyPrefix() {
return propertyPrefix;
}
// ********************************
// Install PLAF
// ********************************
@Override
protected void installDefaults(AbstractButton b) {
super.installDefaults(b);
if(!defaults_initialized) {
icon = UIManager.getIcon(getPropertyPrefix() + "icon");
defaults_initialized = true;
}
}
// ********************************
// Uninstall PLAF
// ********************************
@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
* rather than allocating them in each paint call substantially
* reduced the time it took paint to run. Obviously, this
* method can't be re-entered.
*/
private static Dimension size = new Dimension();
private static Rectangle viewRect = new Rectangle();
private static Rectangle iconRect = new Rectangle();
private static Rectangle textRect = new Rectangle();
/**
* paint the radio button
*/
@Override
public synchronized void paint(Graphics g, JComponent c) {
AbstractButton b = (AbstractButton) c;
ButtonModel model = b.getModel();
Font f = c.getFont();
g.setFont(f);
FontMetrics fm = SwingUtilities2.getFontMetrics(c, g, f);
Insets i = c.getInsets();
size = b.getSize(size);
viewRect.x = i.left;
viewRect.y = i.top;
viewRect.width = size.width - (i.right + viewRect.x);
viewRect.height = size.height - (i.bottom + viewRect.y);
iconRect.x = iconRect.y = iconRect.width = iconRect.height = 0;
textRect.x = textRect.y = textRect.width = textRect.height = 0;
Icon altIcon = b.getIcon();
Icon selectedIcon = null;
Icon disabledIcon = null;
String text = SwingUtilities.layoutCompoundLabel(
c, fm, b.getText(), altIcon != null ? altIcon : getDefaultIcon(),
b.getVerticalAlignment(), b.getHorizontalAlignment(),
b.getVerticalTextPosition(), b.getHorizontalTextPosition(),
viewRect, iconRect, textRect,
b.getText() == null ? 0 : b.getIconTextGap());
// fill background
if(c.isOpaque()) {
g.setColor(b.getBackground());
g.fillRect(0,0, size.width, size.height);
}
// Paint the radio button
if(altIcon != null) {
if(!model.isEnabled()) {
if(model.isSelected()) {
altIcon = b.getDisabledSelectedIcon();
} else {
altIcon = b.getDisabledIcon();
}
} else if(model.isPressed() && model.isArmed()) {
altIcon = b.getPressedIcon();
if(altIcon == null) {
// Use selected icon
altIcon = b.getSelectedIcon();
}
} else if(model.isSelected()) {
if(b.isRolloverEnabled() && model.isRollover()) {
altIcon = b.getRolloverSelectedIcon();
if (altIcon == null) {
altIcon = b.getSelectedIcon();
}
} else {
altIcon = b.getSelectedIcon();
}
} else if(b.isRolloverEnabled() && model.isRollover()) {
altIcon = b.getRolloverIcon();
}
if(altIcon == null) {
altIcon = b.getIcon();
}
altIcon.paintIcon(c, g, iconRect.x, iconRect.y);
} else {
getDefaultIcon().paintIcon(c, g, iconRect.x, iconRect.y);
}
// Draw the Text
if(text != null) {
View v = (View) c.getClientProperty(BasicHTML.propertyKey);
if (v != null) {
v.paint(g, textRect);
} else {
paintText(g, b, textRect, text);
}
if(b.hasFocus() && b.isFocusPainted() &&
textRect.width > 0 && textRect.height > 0 ) {
paintFocus(g, textRect, 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) {
}
/* These Insets/Rectangles are allocated once for all
* RadioButtonUI.getPreferredSize() calls. Re-using rectangles
* rather than allocating them in each call substantially
* reduced the time it took getPreferredSize() to run. Obviously,
* this method can't be re-entered.
*/
private static Rectangle prefViewRect = new Rectangle();
private static Rectangle prefIconRect = new Rectangle();
private static Rectangle prefTextRect = new Rectangle();
private static Insets prefInsets = new Insets(0, 0, 0, 0);
/**
* The preferred size of the radio button
*/
@Override
public Dimension getPreferredSize(JComponent c) {
if(c.getComponentCount() > 0) {
return null;
}
AbstractButton b = (AbstractButton) c;
String text = b.getText();
Icon buttonIcon = b.getIcon();
if(buttonIcon == null) {
buttonIcon = getDefaultIcon();
}
Font font = b.getFont();
FontMetrics fm = b.getFontMetrics(font);
prefViewRect.x = prefViewRect.y = 0;
prefViewRect.width = Short.MAX_VALUE;
prefViewRect.height = Short.MAX_VALUE;
prefIconRect.x = prefIconRect.y = prefIconRect.width = prefIconRect.height = 0;
prefTextRect.x = prefTextRect.y = prefTextRect.width = prefTextRect.height = 0;
SwingUtilities.layoutCompoundLabel(
c, fm, text, buttonIcon,
b.getVerticalAlignment(), b.getHorizontalAlignment(),
b.getVerticalTextPosition(), b.getHorizontalTextPosition(),
prefViewRect, prefIconRect, prefTextRect,
text == null ? 0 : b.getIconTextGap());
// find the union of the icon and text rects (from Rectangle.java)
int x1 = Math.min(prefIconRect.x, prefTextRect.x);
int x2 = Math.max(prefIconRect.x + prefIconRect.width,
prefTextRect.x + prefTextRect.width);
int y1 = Math.min(prefIconRect.y, prefTextRect.y);
int y2 = Math.max(prefIconRect.y + prefIconRect.height,
prefTextRect.y + prefTextRect.height);
int width = x2 - x1;
int height = y2 - y1;
prefInsets = b.getInsets(prefInsets);
width += prefInsets.left + prefInsets.right;
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)) {
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 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) {
}
}
}