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 for its
42 * children buttons components. It makes it possible to transfer focus from
43 * button to button with help of arrows keys.
44 * <p>
45 * The following example shows how to enable cyclic focus transfer
46 *
47 * <pre>
48 * import org.jdesktop.swinghelper.buttonpanel.*;
49 * import javax.swing.*;
50 *
51 * public class SimpleDemo
52 * {
53 * public static void main( String[] args ) throws Exception
54 * {
55 * SwingUtilities.invokeLater( new Runnable()
56 * {
57 * public void run()
58 * {
59 * final JFrame frame = new JFrame();
60 * frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
61 *
62 * JXButtonPanel panel = new JXButtonPanel();
63 * panel.setCyclic( true );
64 *
65 * panel.add( new JButton( "One" ) );
66 * panel.add( new JButton( "Two" ) );
67 * panel.add( new JButton( "Three" ) );
68 *
69 * frame.add( panel );
70 * frame.setSize( 200, 200 );
71 * frame.setLocationRelativeTo( null );
72 * frame.setVisible( true );
73 * }
74 * } );
75 * }
76 * }
77 * </pre>
78 *
79 * If your buttons inside JXButtonPanel are added to one ButtonGroup arrow keys
80 * will transfer selection between them as well as they do it for focus
81 * <p>
82 * Note: you can control this behaviour with
83 * setGroupSelectionFollowFocus(boolean)
84 *
85 * <pre>
86 * import org.jdesktop.swinghelper.buttonpanel.*;
87 * import javax.swing.*;
88 *
89 * public class RadioButtonDemo
90 * {
91 * public static void main( String[] args ) throws Exception
92 * {
93 * SwingUtilities.invokeLater( new Runnable()
94 * {
95 * public void run()
96 * {
97 * final JFrame frame = new JFrame();
98 * frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
99 *
100 * JXButtonPanel panel = new JXButtonPanel();
101 * ButtonGroup group = new ButtonGroup();
102 *
103 * JRadioButton rb1 = new JRadioButton( "One" );
104 * panel.add( rb1 );
105 * group.add( rb1 );
106 * JRadioButton rb2 = new JRadioButton( "Two" );
107 * panel.add( rb2 );
108 * group.add( rb2 );
109 * JRadioButton rb3 = new JRadioButton( "Three" );
110 * panel.add( rb3 );
111 * group.add( rb3 );
112 *
113 * rb1.setSelected( true );
114 * frame.add( panel );
115 *
116 * frame.setSize( 200, 200 );
117 * frame.setLocationRelativeTo( null );
118 * frame.setVisible( true );
119 * }
120 * } );
121 * }
122 * }
123 * </pre>
124 *
125 * @author Alexander Potochkin
126 *
127 * https://swinghelper.dev.java.net/
128 * http://weblogs.java.net/blog/alexfromsun/
129 */
130 public class JXButtonPanel extends JPanel
131 {
132 private boolean isCyclic;
133 private boolean isGroupSelectionFollowFocus;
134
135 /***
136 * {@inheritDoc}
137 */
138 public JXButtonPanel()
139 {
140 super();
141 init();
142 }
143
144 /***
145 * {@inheritDoc}
146 */
147 public JXButtonPanel( LayoutManager layout )
148 {
149 super( layout );
150 init();
151 }
152
153 /***
154 * {@inheritDoc}
155 */
156 public JXButtonPanel( boolean isDoubleBuffered )
157 {
158 super( isDoubleBuffered );
159 init();
160 }
161
162 /***
163 * {@inheritDoc}
164 */
165 public JXButtonPanel( LayoutManager layout, boolean isDoubleBuffered )
166 {
167 super( layout, isDoubleBuffered );
168 init();
169 }
170
171 private void init()
172 {
173 setFocusTraversalPolicyProvider( true );
174 setFocusTraversalPolicy( new JXButtonPanelFocusTraversalPolicy() );
175 ActionListener actionHandler = new ActionHandler();
176 registerKeyboardAction( actionHandler, ActionHandler.FORWARD, KeyStroke.getKeyStroke( KeyEvent.VK_RIGHT, 0 ),
177 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT );
178 registerKeyboardAction( actionHandler, ActionHandler.FORWARD, KeyStroke.getKeyStroke( KeyEvent.VK_DOWN, 0 ),
179 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT );
180 registerKeyboardAction( actionHandler, ActionHandler.BACKWARD, KeyStroke.getKeyStroke( KeyEvent.VK_LEFT, 0 ),
181 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT );
182 registerKeyboardAction( actionHandler, ActionHandler.BACKWARD, KeyStroke.getKeyStroke( KeyEvent.VK_UP, 0 ),
183 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT );
184 setGroupSelectionFollowFocus( true );
185 }
186
187 /***
188 * Returns whether arrow keys should support cyclic focus traversal ordering
189 * for for this JXButtonPanel.
190 */
191 public boolean isCyclic()
192 {
193 return isCyclic;
194 }
195
196 /***
197 * Sets whether arrow keys should support cyclic focus traversal ordering for
198 * this JXButtonPanel.
199 */
200 public void setCyclic( boolean isCyclic )
201 {
202 this.isCyclic = isCyclic;
203 }
204
205 /***
206 * Returns whether arrow keys should transfer button's selection as well as
207 * focus for this JXButtonPanel.
208 * <p>
209 *
210 * Note: this property affects buttons which are added to a ButtonGroup
211 */
212 public boolean isGroupSelectionFollowFocus()
213 {
214 return isGroupSelectionFollowFocus;
215 }
216
217 /***
218 * Sets whether arrow keys should transfer button's selection as well as
219 * focus for this JXButtonPanel.
220 * <p>
221 *
222 * Note: this property affects buttons which are added to a ButtonGroup
223 */
224 public void setGroupSelectionFollowFocus( boolean groupSelectionFollowFocus )
225 {
226 isGroupSelectionFollowFocus = groupSelectionFollowFocus;
227 }
228
229 private static ButtonGroup getButtonGroup( AbstractButton button )
230 {
231 ButtonModel model = button.getModel();
232 if( model instanceof DefaultButtonModel )
233 {
234 return ( ( DefaultButtonModel )model ).getGroup();
235 }
236 return null;
237 }
238
239 private class ActionHandler implements ActionListener
240 {
241 private static final String FORWARD = "moveSelectionForward";
242 private static final String BACKWARD = "moveSelectionBackward";
243
244 public void actionPerformed( ActionEvent e )
245 {
246 FocusTraversalPolicy ftp = JXButtonPanel.this.getFocusTraversalPolicy();
247
248 if( ftp instanceof JXButtonPanelFocusTraversalPolicy )
249 {
250 JXButtonPanelFocusTraversalPolicy xftp = ( JXButtonPanelFocusTraversalPolicy )ftp;
251
252 String actionCommand = e.getActionCommand();
253 Component fo = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
254 Component next;
255
256 xftp.setAlternativeFocusMode( true );
257
258 if( FORWARD.equals( actionCommand ) )
259 {
260 next = xftp.getComponentAfter( JXButtonPanel.this, fo );
261 }
262 else if( BACKWARD.equals( actionCommand ) )
263 {
264 next = xftp.getComponentBefore( JXButtonPanel.this, fo );
265 }
266 else
267 {
268 throw new AssertionError( "Unexpected action command: " + actionCommand );
269 }
270
271 xftp.setAlternativeFocusMode( false );
272
273 if( fo instanceof AbstractButton )
274 {
275 AbstractButton b = ( AbstractButton )fo;
276 b.getModel().setPressed( false );
277 }
278 if( next != null )
279 {
280 if( fo instanceof AbstractButton && next instanceof AbstractButton )
281 {
282 ButtonGroup group = getButtonGroup( ( AbstractButton )fo );
283 AbstractButton nextButton = ( AbstractButton )next;
284 if( group != getButtonGroup( nextButton ) )
285 {
286 return;
287 }
288 if( isGroupSelectionFollowFocus() && group != null && group.getSelection() != null
289 && !nextButton.isSelected() )
290 {
291 nextButton.setSelected( true );
292 }
293 next.requestFocusInWindow();
294 }
295 }
296 }
297 }
298 }
299
300 private class JXButtonPanelFocusTraversalPolicy extends LayoutFocusTraversalPolicy
301 {
302 private boolean isAlternativeFocusMode;
303
304 public boolean isAlternativeFocusMode()
305 {
306 return isAlternativeFocusMode;
307 }
308
309 public void setAlternativeFocusMode( boolean alternativeFocusMode )
310 {
311 isAlternativeFocusMode = alternativeFocusMode;
312 }
313
314 protected boolean accept( Component c )
315 {
316 if( !isAlternativeFocusMode() && c instanceof AbstractButton )
317 {
318 AbstractButton button = ( AbstractButton )c;
319 ButtonGroup group = JXButtonPanel.getButtonGroup( button );
320 if( group != null && group.getSelection() != null && !button.isSelected() )
321 {
322 return false;
323 }
324 }
325 return super.accept( c );
326 }
327
328 public Component getComponentAfter( Container aContainer, Component aComponent )
329 {
330 Component componentAfter = super.getComponentAfter( aContainer, aComponent );
331 if( !isAlternativeFocusMode() )
332 {
333 return componentAfter;
334 }
335 if( JXButtonPanel.this.isCyclic() )
336 {
337 return componentAfter == null ? getFirstComponent( aContainer ) : componentAfter;
338 }
339 if( aComponent == getLastComponent( aContainer ) )
340 {
341 return aComponent;
342 }
343 return componentAfter;
344 }
345
346 public Component getComponentBefore( Container aContainer, Component aComponent )
347 {
348 Component componentBefore = super.getComponentBefore( aContainer, aComponent );
349 if( !isAlternativeFocusMode() )
350 {
351 return componentBefore;
352 }
353 if( JXButtonPanel.this.isCyclic() )
354 {
355 return componentBefore == null ? getLastComponent( aContainer ) : componentBefore;
356 }
357 if( aComponent == getFirstComponent( aContainer ) )
358 {
359 return aComponent;
360 }
361 return componentBefore;
362 }
363 }
364 }