/*
* Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle,
* Santa Clara, California 95054, U.S.A. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.FocusTraversalPolicy;
import java.awt.GridLayout;
import java.awt.KeyboardFocusManager;
import java.awt.LayoutManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.ButtonModel;
import javax.swing.DefaultButtonModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.KeyStroke;
import javax.swing.LayoutFocusTraversalPolicy;
import javax.swing.SwingUtilities;
import javax.swing.border.TitledBorder;
/**
* This is a JPanel subclass which provides a special functionality
* for its children buttons components.
* It makes it possible to transfer focus from button to button
* with help of arrows keys.
* The following example shows how to enable cyclic focus transfer
*
* import org.jdesktop.swinghelper.buttonpanel.*;
* import javax.swing.*;
*
* public class SimpleDemo {
* public static void main(String[] args) throws Exception {
* SwingUtilities.invokeLater(new Runnable() {
* public void run() {
* final JFrame frame = new JFrame();
* frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
*
* JXButtonPanel panel = new JXButtonPanel();
* panel.setCyclic(true);
*
* panel.add(new JButton("One"));
* panel.add(new JButton("Two"));
* panel.add(new JButton("Three"));
*
* frame.add(panel);
* frame.setSize(200, 200);
* frame.setLocationRelativeTo(null);
* frame.setVisible(true);
* }
* });
* }
* }
*
*
* If your buttons inside JXButtonPanel are added to one ButtonGroup
* arrow keys will transfer selection between them as well as they do it for focus
* Note: you can control this behaviour with setGroupSelectionFollowFocus(boolean)
*
* import org.jdesktop.swinghelper.buttonpanel.*;
* import javax.swing.*;
*
* public class RadioButtonDemo {
* public static void main(String[] args) throws Exception {
* SwingUtilities.invokeLater(new Runnable() {
* public void run() {
* final JFrame frame = new JFrame();
* frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
*
* JXButtonPanel panel = new JXButtonPanel();
* ButtonGroup group = new ButtonGroup();
*
* JRadioButton rb1 = new JRadioButton("One");
* panel.add(rb1);
* group.add(rb1);
* JRadioButton rb2 = new JRadioButton("Two");
* panel.add(rb2);
* group.add(rb2);
* JRadioButton rb3 = new JRadioButton("Three");
* panel.add(rb3);
* group.add(rb3);
*
* rb1.setSelected(true);
* frame.add(panel);
*
* frame.setSize(200, 200);
* frame.setLocationRelativeTo(null);
* frame.setVisible(true);
* }
* });
* }
* }
*
*
* @author Alexander Potochkin
*
* https://swinghelper.dev.java.net/
* http://weblogs.java.net/blog/alexfromsun/
*/
public class JXButtonPanel extends JPanel {
private boolean isCyclic;
private boolean isGroupSelectionFollowFocus;
/**
* {@inheritDoc}
*/
public JXButtonPanel() {
super();
init();
}
/**
* {@inheritDoc}
*/
public JXButtonPanel(LayoutManager layout) {
super(layout);
init();
}
/**
* {@inheritDoc}
*/
public JXButtonPanel(boolean isDoubleBuffered) {
super(isDoubleBuffered);
init();
}
/**
* {@inheritDoc}
*/
public JXButtonPanel(LayoutManager layout, boolean isDoubleBuffered) {
super(layout, isDoubleBuffered);
init();
}
private void init() {
setFocusTraversalPolicyProvider(true);
setFocusTraversalPolicy(new JXButtonPanelFocusTraversalPolicy());
ActionListener actionHandler = new ActionHandler();
registerKeyboardAction(actionHandler, ActionHandler.FORWARD,
KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0),
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
registerKeyboardAction(actionHandler, ActionHandler.FORWARD,
KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0),
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
registerKeyboardAction(actionHandler, ActionHandler.BACKWARD,
KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0),
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
registerKeyboardAction(actionHandler, ActionHandler.BACKWARD,
KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0),
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
setGroupSelectionFollowFocus(true);
}
/**
* Returns whether arrow keys should support
* cyclic focus traversal ordering for for this JXButtonPanel.
*/
public boolean isCyclic() {
return isCyclic;
}
/**
* Sets whether arrow keys should support
* cyclic focus traversal ordering for this JXButtonPanel.
*/
public void setCyclic(boolean isCyclic) {
this.isCyclic = isCyclic;
}
/**
* Returns whether arrow keys should transfer button's
* selection as well as focus for this JXButtonPanel.
*
* Note: this property affects buttons which are added to a ButtonGroup
*/
public boolean isGroupSelectionFollowFocus() {
return isGroupSelectionFollowFocus;
}
/**
* Sets whether arrow keys should transfer button's
* selection as well as focus for this JXButtonPanel.
*
* Note: this property affects buttons which are added to a ButtonGroup
*/
public void setGroupSelectionFollowFocus(boolean groupSelectionFollowFocus) {
isGroupSelectionFollowFocus = groupSelectionFollowFocus;
}
private static ButtonGroup getButtonGroup(AbstractButton button) {
ButtonModel model = button.getModel();
if (model instanceof DefaultButtonModel) {
return ((DefaultButtonModel) model).getGroup();
}
return null;
}
private class ActionHandler implements ActionListener {
private static final String FORWARD = "moveSelectionForward";
private static final String BACKWARD = "moveSelectionBackward";
public void actionPerformed(ActionEvent e) {
FocusTraversalPolicy ftp = JXButtonPanel.this.getFocusTraversalPolicy();
if (ftp instanceof JXButtonPanelFocusTraversalPolicy) {
JXButtonPanelFocusTraversalPolicy xftp =
(JXButtonPanelFocusTraversalPolicy) ftp;
String actionCommand = e.getActionCommand();
Component fo =
KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
Component next;
xftp.setAlternativeFocusMode(true);
if (FORWARD.equals(actionCommand)) {
next = xftp.getComponentAfter(JXButtonPanel.this, fo);
} else if (BACKWARD.equals(actionCommand)) {
next = xftp.getComponentBefore(JXButtonPanel.this, fo);
} else {
throw new AssertionError("Unexpected action command: " + actionCommand);
}
xftp.setAlternativeFocusMode(false);
if (fo instanceof AbstractButton) {
AbstractButton b = (AbstractButton) fo;
b.getModel().setPressed(false);
}
if (next != null) {
if (fo instanceof AbstractButton && next instanceof AbstractButton) {
ButtonGroup group = getButtonGroup((AbstractButton) fo);
AbstractButton nextButton = (AbstractButton) next;
if (group != getButtonGroup(nextButton)) {
return;
}
if (isGroupSelectionFollowFocus() && group != null &&
group.getSelection() != null && !nextButton.isSelected()) {
nextButton.setSelected(true);
}
next.requestFocusInWindow();
}
}
}
}
}
private class JXButtonPanelFocusTraversalPolicy extends LayoutFocusTraversalPolicy {
private boolean isAlternativeFocusMode;
public boolean isAlternativeFocusMode() {
return isAlternativeFocusMode;
}
public void setAlternativeFocusMode(boolean alternativeFocusMode) {
isAlternativeFocusMode = alternativeFocusMode;
}
protected boolean accept(Component c) {
if (!isAlternativeFocusMode() && c instanceof AbstractButton) {
AbstractButton button = (AbstractButton) c;
ButtonGroup group = JXButtonPanel.getButtonGroup(button);
if (group != null && group.getSelection() != null
&& !button.isSelected()) {
return false;
}
}
return super.accept(c);
}
public Component getComponentAfter(Container aContainer, Component aComponent) {
Component componentAfter = super.getComponentAfter(aContainer, aComponent);
if (!isAlternativeFocusMode()) {
return componentAfter;
}
if (JXButtonPanel.this.isCyclic()) {
return componentAfter == null ?
getFirstComponent(aContainer) : componentAfter;
}
if (aComponent == getLastComponent(aContainer)) {
return aComponent;
}
return componentAfter;
}
public Component getComponentBefore(Container aContainer, Component aComponent) {
Component componentBefore = super.getComponentBefore(aContainer, aComponent);
if (!isAlternativeFocusMode()) {
return componentBefore;
}
if (JXButtonPanel.this.isCyclic()) {
return componentBefore == null ?
getLastComponent(aContainer) : componentBefore;
}
if (aComponent == getFirstComponent(aContainer)) {
return aComponent;
}
return componentBefore;
}
}
}
/**
* @author Alexander Potochkin
*
* https://swinghelper.dev.java.net/
* http://weblogs.java.net/blog/alexfromsun/
*/
class JXButtonPanelDemo extends JFrame {
private ButtonGroup radioGroup = new ButtonGroup();
public JXButtonPanelDemo() {
super("JXButtonPanel demo");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
JPanel topPanel = new JPanel(new GridLayout(1, 0));
final JXButtonPanel radioGroupPanel = createRadioJXButtonPanel();
topPanel.add(radioGroupPanel);
final JXButtonPanel checkBoxPanel = createCheckBoxJXButtonPanel();
topPanel.add(checkBoxPanel);
add(topPanel);
add(createButtonJXButtonPanel(), BorderLayout.SOUTH);
pack();
JMenuBar bar = new JMenuBar();
JMenu menu = new JMenu("Options");
JMenuItem item = new JMenuItem("Unselect radioButtons");
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.ALT_MASK));
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// hack for 1.5
// in 1.6 ButtonGroup.clearSelection added
JRadioButton b = new JRadioButton();
radioGroup.add(b);
b.setSelected(true);
radioGroup.remove(b);
}
});
menu.add(item);
bar.add(menu);
setJMenuBar(bar);
setSize(300, 300);
setLocationRelativeTo(null);
}
private JXButtonPanel createRadioJXButtonPanel() {
JXButtonPanel panel = new JXButtonPanel();
panel.setLayout(new GridLayout(0, 1));
JRadioButton one = new JRadioButton("One");
panel.add(one);
radioGroup.add(one);
JRadioButton two = new JRadioButton("Two");
panel.add(two);
radioGroup.add(two);
JRadioButton three = new JRadioButton("Three");
panel.add(three);
radioGroup.add(three);
JRadioButton four = new JRadioButton("Four");
panel.add(four);
radioGroup.add(four);
one.setSelected(true);
panel.setBorder(BorderFactory.createTitledBorder("JXButtonPanel"));
return panel;
}
private JXButtonPanel createCheckBoxJXButtonPanel() {
JXButtonPanel panel = new JXButtonPanel();
panel.setLayout(new GridLayout(0, 1));
JCheckBox one = new JCheckBox("One");
panel.add(one);
JCheckBox two = new JCheckBox("Two");
panel.add(two);
JCheckBox three = new JCheckBox("Three");
panel.add(three);
JCheckBox four = new JCheckBox("Four");
panel.add(four);
panel.setBorder(BorderFactory.createTitledBorder("JXButtonPanel"));
return panel;
}
private JPanel createButtonJXButtonPanel() {
JPanel ret = new JPanel(new BorderLayout());
JLabel label = new JLabel("Does JXButtonPanel support arrow keys ?");
label.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
JPanel temp = new JPanel();
temp.add(label);
ret.add(temp);
JXButtonPanel panel = new JXButtonPanel();
panel.setCyclic(true);
panel.add(new JButton("Yes"));
panel.add(new JButton("Sure"));
panel.add(new JButton("Absolutely !"));
panel.setBorder(BorderFactory.createTitledBorder(null,
"JXButtonPanel.setCyclic(true)",
TitledBorder.CENTER, TitledBorder.BOTTOM));
ret.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0));
ret.add(panel, BorderLayout.SOUTH);
return ret;
}
public static void main(String[] args) throws Exception {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new JXButtonPanelDemo().setVisible(true);
}
});
}
}