package net.xoetrope.swing.focus;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.FocusTraversalPolicy;
import java.awt.KeyboardFocusManager;
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.KeyStroke;
import javax.swing.LayoutFocusTraversalPolicy;
import javax.swing.SwingUtilities;
import net.xoetrope.swing.XPanel;
import net.xoetrope.xui.helper.ReflectionHelper;
/**
* The class is an extract of the SwingLabs JXButtonPanel. It handles the
* change of focus using the arrow keys.
* <p>Copyright (c) Xoetrope Ltd., 1998-2004<br>
* License: see license.txt
* @version 1.0
*/
public class XPanelFocusExtension
{
private boolean isCyclic;
private boolean isGroupSelectionFollowFocus;
private XPanel xp;
/** Creates a new instance of XPanelFocusExtension */
public XPanelFocusExtension( XPanel xp )
{
this.xp = xp;
Object[] values = new Object[ 1 ];
values[ 0 ] = Boolean.TRUE;
ReflectionHelper.setViaReflection( "setFocusTraversalPolicyProvider", xp, values );
//xp.setFocusTraversalPolicyProvider( true );
xp.setFocusTraversalPolicy( new JXButtonPanelFocusTraversalPolicy());
ActionListener actionHandler = new ActionHandler();
xp.registerKeyboardAction( actionHandler, ActionHandler.FORWARD,
KeyStroke.getKeyStroke( KeyEvent.VK_RIGHT, 0 ),
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT );
xp.registerKeyboardAction( actionHandler, ActionHandler.FORWARD,
KeyStroke.getKeyStroke( KeyEvent.VK_DOWN, 0 ),
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT );
xp.registerKeyboardAction( actionHandler, ActionHandler.BACKWARD,
KeyStroke.getKeyStroke( KeyEvent.VK_LEFT, 0 ),
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT );
xp.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 = xp.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( xp, fo );
}
else if (
BACKWARD.equals( actionCommand )) {
next = xftp.getComponentBefore( xp, 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 = XPanelFocusExtension.this.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 ( XPanelFocusExtension.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 ( XPanelFocusExtension.this.isCyclic()) {
return componentBefore == null ? getLastComponent(aContainer) : componentBefore;
}
if (aComponent == getFirstComponent(aContainer)) {
return aComponent;
}
return componentBefore;
}
}
}