/* * SoapUI, Copyright (C) 2004-2016 SmartBear Software * * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent * versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: * * http://ec.europa.eu/idabc/eupl * * Unless required by applicable law or agreed to in writing, software distributed under the Licence is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the Licence for the specific language governing permissions and limitations * under the Licence. */ package com.eviware.soapui.support.swing; 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; 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; /** * 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 * <p/> * <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> * <p/> * 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) * <p/> * <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 * <p/> * 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/> * <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/> * <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; } } }