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 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( &quot;One&quot; ) );
66   * 				panel.add( new JButton( &quot;Two&quot; ) );
67   * 				panel.add( new JButton( &quot;Three&quot; ) );
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( &quot;One&quot; );
104  * 				panel.add( rb1 );
105  * 				group.add( rb1 );
106  * 				JRadioButton rb2 = new JRadioButton( &quot;Two&quot; );
107  * 				panel.add( rb2 );
108  * 				group.add( rb2 );
109  * 				JRadioButton rb3 = new JRadioButton( &quot;Three&quot; );
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 }