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 }