View Javadoc

    1   /***
    2    * Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle,
    3    * Santa Clara, California 95054, U.S.A. All rights reserved.
    4    *
    5    * This library is free software; you can redistribute it and/or
    6    * modify it under the terms of the GNU Lesser General Public
    7    * License as published by the Free Software Foundation; either
    8    * version 2.1 of the License, or (at your option) any later version.
    9    *
    10   * This library is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    13   * Lesser General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU Lesser General Public
    16   * License along with this library; if not, write to the Free Software
    17   * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
    18   */
    19
    20  package com.eviware.soapui.support.swing;
    21
    22  import java.awt.Component;
    23  import java.awt.Container;
    24  import java.awt.FocusTraversalPolicy;
    25  import java.awt.KeyboardFocusManager;
    26  import java.awt.LayoutManager;
    27  import java.awt.event.ActionEvent;
    28  import java.awt.event.ActionListener;
    29  import java.awt.event.KeyEvent;
    30
    31  import javax.swing.AbstractButton;
    32  import javax.swing.ButtonGroup;
    33  import javax.swing.ButtonModel;
    34  import javax.swing.DefaultButtonModel;
    35  import javax.swing.JComponent;
    36  import javax.swing.JPanel;
    37  import javax.swing.KeyStroke;
    38  import javax.swing.LayoutFocusTraversalPolicy;
    39
    40  /***
    41   * This is a JPanel subclass which provides a special functionality
    42   * for its children buttons components.
    43   * It makes it possible to transfer focus from button to button
    44   * with help of arrows keys.
    45   * <p>The following example shows how to enable cyclic focus transfer
    46   * <pre>
    47   * import org.jdesktop.swinghelper.buttonpanel.*;
    48   * import javax.swing.*;
    49   *
    50   * public class SimpleDemo {
    51   *     public static void main(String[] args) throws Exception {
    52   *         SwingUtilities.invokeLater(new Runnable() {
    53   *             public void run() {
    54   *                 final JFrame frame = new JFrame();
    55   *                 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    56   *
    57   *                 JXButtonPanel panel = new JXButtonPanel();
    58   *                 panel.setCyclic(true);
    59   *
    60   *                 panel.add(new JButton("One"));
    61   *                 panel.add(new JButton("Two"));
    62   *                 panel.add(new JButton("Three"));
    63   *
    64   *                 frame.add(panel);
    65   *                 frame.setSize(200, 200);
    66   *                 frame.setLocationRelativeTo(null);
    67   *                 frame.setVisible(true);
    68   *             }
    69   *         });
    70   *     }
    71   * }
    72   * </pre>
    73   *
    74   * If your buttons inside JXButtonPanel are added to one ButtonGroup
    75   * arrow keys will transfer selection between them as well as they do it for focus<p>
    76   * Note: you can control this behaviour with setGroupSelectionFollowFocus(boolean)
    77   * <pre>
    78   * import org.jdesktop.swinghelper.buttonpanel.*;
    79   * import javax.swing.*;
    80   *
    81   * public class RadioButtonDemo {
    82   *     public static void main(String[] args) throws Exception {
    83   *         SwingUtilities.invokeLater(new Runnable() {
    84   *             public void run() {
    85   *                 final JFrame frame = new JFrame();
    86   *                 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    87   *
    88   *                 JXButtonPanel panel = new JXButtonPanel();
    89   *                 ButtonGroup group = new ButtonGroup();
    90   *
    91   *                 JRadioButton rb1 = new JRadioButton("One");
    92   *                 panel.add(rb1);
    93   *                 group.add(rb1);
    94   *                 JRadioButton rb2 = new JRadioButton("Two");
    95   *                 panel.add(rb2);
    96   *                 group.add(rb2);
    97   *                 JRadioButton rb3 = new JRadioButton("Three");
    98   *                 panel.add(rb3);
    99   *                 group.add(rb3);
    100  *
    101  *                 rb1.setSelected(true);
    102  *                 frame.add(panel);
    103  *
    104  *                 frame.setSize(200, 200);
    105  *                 frame.setLocationRelativeTo(null);
    106  *                 frame.setVisible(true);
    107  *             }
    108  *         });
    109  *     }
    110  * }
    111  * </pre>
    112  *
    113  * @author Alexander Potochkin
    114  *
    115  * https://swinghelper.dev.java.net/
    116  * http://weblogs.java.net/blog/alexfromsun/
    117  */
    118 public class JXButtonPanel extends JPanel {
    119     private boolean isCyclic;
    120     private boolean isGroupSelectionFollowFocus;
    121
    122     /***
    123      * {@inheritDoc}
    124      */
    125     public JXButtonPanel() {
    126         super();
    127         init();
    128     }
    129
    130     /***
    131      * {@inheritDoc}
    132      */
    133     public JXButtonPanel(LayoutManager layout) {
    134         super(layout);
    135         init();
    136     }
    137
    138     /***
    139      * {@inheritDoc}
    140      */
    141     public JXButtonPanel(boolean isDoubleBuffered) {
    142         super(isDoubleBuffered);
    143         init();
    144     }
    145
    146     /***
    147      * {@inheritDoc}
    148      */
    149     public JXButtonPanel(LayoutManager layout, boolean isDoubleBuffered) {
    150         super(layout, isDoubleBuffered);
    151         init();
    152     }
    153
    154     private void init() {
    155         setFocusTraversalPolicyProvider(true);
    156         setFocusTraversalPolicy(new JXButtonPanelFocusTraversalPolicy());
    157         ActionListener actionHandler = new ActionHandler();
    158         registerKeyboardAction(actionHandler, ActionHandler.FORWARD,
    159                 KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0),
    160                 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
    161         registerKeyboardAction(actionHandler, ActionHandler.FORWARD,
    162                 KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0),
    163                 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
    164         registerKeyboardAction(actionHandler, ActionHandler.BACKWARD,
    165                 KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0),
    166                 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
    167         registerKeyboardAction(actionHandler, ActionHandler.BACKWARD,
    168                 KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0),
    169                 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
    170         setGroupSelectionFollowFocus(true);
    171     }
    172
    173     /***
    174      * Returns whether arrow keys should support
    175      * cyclic focus traversal ordering for for this JXButtonPanel.
    176      */
    177     public boolean isCyclic() {
    178         return isCyclic;
    179     }
    180
    181     /***
    182      * Sets whether arrow keys should support
    183      * cyclic focus traversal ordering for this JXButtonPanel.
    184      */
    185     public void setCyclic(boolean isCyclic) {
    186         this.isCyclic = isCyclic;
    187     }
    188
    189     /***
    190      * Returns whether arrow keys should transfer button's
    191      * selection as well as focus for this JXButtonPanel.<p>
    192      *
    193      * Note: this property affects buttons which are added to a ButtonGroup
    194      */
    195     public boolean isGroupSelectionFollowFocus() {
    196         return isGroupSelectionFollowFocus;
    197     }
    198
    199     /***
    200      * Sets whether arrow keys should transfer button's
    201      * selection as well as focus for this JXButtonPanel.<p>
    202      *
    203      * Note: this property affects buttons which are added to a ButtonGroup
    204      */
    205     public void setGroupSelectionFollowFocus(boolean groupSelectionFollowFocus) {
    206         isGroupSelectionFollowFocus = groupSelectionFollowFocus;
    207     }
    208
    209     private static ButtonGroup getButtonGroup(AbstractButton button) {
    210         ButtonModel model = button.getModel();
    211         if (model instanceof DefaultButtonModel) {
    212             return ((DefaultButtonModel) model).getGroup();
    213         }
    214         return null;
    215     }
    216
    217     private class ActionHandler implements ActionListener {
    218         private static final String FORWARD = "moveSelectionForward";
    219         private static final String BACKWARD = "moveSelectionBackward";
    220
    221         public void actionPerformed(ActionEvent e) {
    222             FocusTraversalPolicy ftp = JXButtonPanel.this.getFocusTraversalPolicy();
    223
    224             if (ftp instanceof JXButtonPanelFocusTraversalPolicy) {
    225                 JXButtonPanelFocusTraversalPolicy xftp =
    226                         (JXButtonPanelFocusTraversalPolicy) ftp;
    227
    228                 String actionCommand = e.getActionCommand();
    229                 Component fo =
    230                         KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
    231                 Component next;
    232
    233                 xftp.setAlternativeFocusMode(true);
    234
    235                 if (FORWARD.equals(actionCommand)) {
    236                     next = xftp.getComponentAfter(JXButtonPanel.this, fo);
    237                 } else if (BACKWARD.equals(actionCommand)) {
    238                     next = xftp.getComponentBefore(JXButtonPanel.this, fo);
    239                 } else {
    240                     throw new AssertionError("Unexpected action command: " + actionCommand);
    241                 }
    242
    243                 xftp.setAlternativeFocusMode(false);
    244
    245                 if (fo instanceof AbstractButton) {
    246                     AbstractButton b = (AbstractButton) fo;
    247                     b.getModel().setPressed(false);
    248                 }
    249                 if (next != null) {
    250                     if (fo instanceof AbstractButton && next instanceof AbstractButton) {
    251                         ButtonGroup group = getButtonGroup((AbstractButton) fo);
    252                         AbstractButton nextButton = (AbstractButton) next;
    253                         if (group != getButtonGroup(nextButton)) {
    254                             return;
    255                         }
    256                         if (isGroupSelectionFollowFocus() && group != null &&
    257                                 group.getSelection() != null && !nextButton.isSelected()) {
    258                             nextButton.setSelected(true);
    259                         }
    260                         next.requestFocusInWindow();
    261                     }
    262                 }
    263             }
    264         }
    265     }
    266
    267     private class JXButtonPanelFocusTraversalPolicy extends LayoutFocusTraversalPolicy {
    268         private boolean isAlternativeFocusMode;
    269
    270         public boolean isAlternativeFocusMode() {
    271             return isAlternativeFocusMode;
    272         }
    273
    274         public void setAlternativeFocusMode(boolean alternativeFocusMode) {
    275             isAlternativeFocusMode = alternativeFocusMode;
    276         }
    277
    278         protected boolean accept(Component c) {
    279             if (!isAlternativeFocusMode() && c instanceof AbstractButton) {
    280                 AbstractButton button = (AbstractButton) c;
    281                 ButtonGroup group = JXButtonPanel.getButtonGroup(button);
    282                 if (group != null && group.getSelection() != null
    283                         && !button.isSelected()) {
    284                     return false;
    285                 }
    286             }
    287             return super.accept(c);
    288         }
    289
    290         public Component getComponentAfter(Container aContainer, Component aComponent) {
    291             Component componentAfter = super.getComponentAfter(aContainer, aComponent);
    292             if (!isAlternativeFocusMode()) {
    293                 return componentAfter;
    294             }
    295             if (JXButtonPanel.this.isCyclic()) {
    296                 return componentAfter == null ?
    297                         getFirstComponent(aContainer) : componentAfter;
    298             }
    299             if (aComponent == getLastComponent(aContainer)) {
    300                 return aComponent;
    301             }
    302             return componentAfter;
    303         }
    304
    305         public Component getComponentBefore(Container aContainer, Component aComponent) {
    306             Component componentBefore = super.getComponentBefore(aContainer, aComponent);
    307             if (!isAlternativeFocusMode()) {
    308                 return componentBefore;
    309             }
    310             if (JXButtonPanel.this.isCyclic()) {
    311                 return componentBefore == null ?
    312                         getLastComponent(aContainer) : componentBefore;
    313             }
    314             if (aComponent == getFirstComponent(aContainer)) {
    315                 return aComponent;
    316             }
    317             return componentBefore;
    318         }
    319     }
    320 }