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 }