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 }