/*
* soapUI, copyright (C) 2004-2011 eviware.com
*
* soapUI is free software; you can redistribute it and/or modify it under the
* terms of version 2.1 of the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* soapUI 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 at gnu.org.
*/
package com.eviware.soapui.support.swing;
import java.awt.Component;
import java.awt.Container;
import java.awt.FocusTraversalPolicy;
import java.awt.KeyboardFocusManager;
import java.awt.LayoutManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.AbstractButton;
import javax.swing.ButtonGroup;
import javax.swing.ButtonModel;
import javax.swing.DefaultButtonModel;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.LayoutFocusTraversalPolicy;
/**
* 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.
* <p>
* The following example shows how to enable cyclic focus transfer
*
* <pre>
* 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 );
* }
* } );
* }
* }
* </pre>
*
* 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
* <p>
* Note: you can control this behaviour with
* setGroupSelectionFollowFocus(boolean)
*
* <pre>
* 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 );
* }
* } );
* }
* }
* </pre>
*
* @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.
* <p>
*
* 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.
* <p>
*
* 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;
}
}
}