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 }