001 /*
002 * $Id: JXRadioGroup.java 3348 2009-05-25 04:09:03Z kschaefe $
003 *
004 * Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
020 */
021 package org.jdesktop.swingx;
022
023 import java.awt.Component;
024 import java.awt.event.ActionEvent;
025 import java.awt.event.ActionListener;
026 import java.awt.event.ItemEvent;
027 import java.awt.event.ItemListener;
028 import java.util.ArrayList;
029 import java.util.Enumeration;
030 import java.util.List;
031
032 import javax.swing.AbstractButton;
033 import javax.swing.BoxLayout;
034 import javax.swing.ButtonGroup;
035 import javax.swing.ButtonModel;
036 import javax.swing.JComponent;
037 import javax.swing.JPanel;
038 import javax.swing.JRadioButton;
039 import javax.swing.event.EventListenerList;
040
041 /**
042 * <p>
043 * {@code JXRadioGroup} is a group of radio buttons that functions as a unit. It
044 * is similar in concept to a {@link JComboBox} in functionality, but can offer
045 * a better presentation for a small number of choices. {@code JXRadioGroup}
046 * should be used in preference to {@code JComboBox} when the number of choices
047 * is small (less than six) or the choices are verbose.
048 * </p>
049 * <p>
050 * Notes:
051 * <ol>
052 * <li>Enabling and disabling the JXRadioGroup will enable/disable all of the
053 * child buttons inside the JXRadioGroup.</li>
054 * <li>
055 * If the generic type parameter of JXRadioGroup is a subclass of
056 * {@link AbstractButton}, then the buttons will be added "as is" to the
057 * container. If the generic type is anything else, buttons will be created as
058 * {@link JRadioButton} objects, and the button text will be set by calling
059 * toString() on the value object.</li>
060 * <li>
061 * Alternatively, if you want to configure the buttons individually, construct
062 * the JXRadioGroup normally, and then call {@link #getChildButton(int)} or
063 * {@link #getChildButton(Object)} and configure the buttons.</li>
064 * </ol>
065 * </p>
066 * <p>
067 * TODO back with a model (possibly reuse of extend {@link ComboBoxModel}
068 * </p>
069 *
070 * @author Amy Fowler
071 * @author Noel Grandin
072 * @version 1.0
073 */
074 public class JXRadioGroup<T> extends JPanel {
075
076 private static final long serialVersionUID = 3257285842266567986L;
077
078 private ButtonGroup buttonGroup;
079
080 private final List<T> values = new ArrayList<T>();
081
082 private ActionSelectionListener actionHandler;
083
084 private List<ActionListener> actionListeners;
085
086 /**
087 * Create a default JXRadioGroup with a default layout axis of {@link BoxLayout#X_AXIS}.
088 */
089 public JXRadioGroup() {
090 setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
091 buttonGroup = new ButtonGroup();
092 }
093
094 /**
095 * Create a default JXRadioGroup with a default layout axis of {@link BoxLayout#X_AXIS}.
096 *
097 * @param radioValues the list of values used to create the group.
098 */
099 public JXRadioGroup(T[] radioValues) {
100 this();
101 for (int i = 0; i < radioValues.length; i++) {
102 add(radioValues[i]);
103 }
104 }
105
106 /**
107 * Convenience factory method.
108 * Reduces code clutter when dealing with generics.
109 *
110 * @param radioValues the list of values used to create the group.
111 */
112 public static <T> JXRadioGroup<T> create(T[] radioValues)
113 {
114 return new JXRadioGroup<T>(radioValues);
115 }
116
117 /**
118 * Set the layout axis of the radio group.
119 *
120 * @param axis values from {@link BoxLayout}.
121 */
122 public void setLayoutAxis(int axis)
123 {
124 setLayout(new BoxLayout(this, axis));
125 }
126
127 /**
128 * Sets the values backing this group. This replaces the current set of
129 * values with the new set.
130 *
131 * @param radioValues
132 * the new backing values for this group
133 */
134 public void setValues(T[] radioValues) {
135 clearAll();
136 for (int i = 0; i < radioValues.length; i++) {
137 add(radioValues[i]);
138 }
139 }
140
141 private void clearAll() {
142 values.clear();
143 buttonGroup = new ButtonGroup();
144 // remove all the child components
145 removeAll();
146 }
147
148 /**
149 * You can use this method to manually add your own AbstractButton objects, provided you declared
150 * the class as <code>JXRadioGroup<JRadioButton></code>.
151 */
152 public void add(T radioValue) {
153 if (values.contains(radioValue))
154 {
155 throw new IllegalArgumentException("cannot add the same value twice " + radioValue);
156 }
157 if (radioValue instanceof AbstractButton) {
158 values.add(radioValue);
159 addButton((AbstractButton) radioValue);
160 } else {
161 values.add(radioValue);
162 // Note: the "quote + object" trick here allows null values
163 addButton(new JRadioButton(""+radioValue));
164 }
165 }
166
167 private void addButton(AbstractButton button) {
168 buttonGroup.add(button);
169 super.add(button);
170 if (actionHandler == null) {
171 actionHandler = new ActionSelectionListener();
172 }
173 button.addActionListener(actionHandler);
174 button.addItemListener(actionHandler);
175 }
176
177 private class ActionSelectionListener implements ActionListener, ItemListener
178 {
179 public void actionPerformed(ActionEvent e) {
180 fireActionEvent(e);
181 }
182
183 public void itemStateChanged(ItemEvent e) {
184 fireActionEvent(null);
185 }
186 }
187
188 /**
189 * Gets the currently selected button.
190 *
191 * @return the currently selected button
192 * @see #getSelectedValue()
193 */
194 public AbstractButton getSelectedButton() {
195 final ButtonModel selectedModel = buttonGroup.getSelection();
196 final AbstractButton children[] = getButtonComponents();
197 for (int i = 0; i < children.length; i++) {
198 AbstractButton button = children[i];
199 if (button.getModel() == selectedModel) {
200 return button;
201 }
202 }
203 return null;
204 }
205
206 private AbstractButton[] getButtonComponents() {
207 final Component[] children = getComponents();
208 final List<AbstractButton> buttons = new ArrayList<AbstractButton>();
209 for (int i = 0; i < children.length; i++) {
210 if (children[i] instanceof AbstractButton) {
211 buttons.add((AbstractButton) children[i]);
212 }
213 }
214 return buttons.toArray(new AbstractButton[buttons.size()]);
215 }
216
217 private int getSelectedIndex() {
218 final ButtonModel selectedModel = buttonGroup.getSelection();
219 final Component children[] = getButtonComponents();
220 for (int i = 0; i < children.length; i++) {
221 AbstractButton button = (AbstractButton) children[i];
222 if (button.getModel() == selectedModel) {
223 return i;
224 }
225 }
226 return -1;
227 }
228
229 /**
230 * The currently selected value.
231 *
232 * @return the current value
233 */
234 public T getSelectedValue() {
235 final int index = getSelectedIndex();
236 return (index < 0 || index >= values.size()) ? null : values.get(index);
237 }
238
239 /**
240 * Selects the supplied value.
241 *
242 * @param value
243 * the value to select
244 */
245 public void setSelectedValue(T value) {
246 final int index = values.indexOf(value);
247 AbstractButton button = getButtonComponents()[index];
248 button.setSelected(true);
249 }
250
251 /**
252 * Retrieve the child button by index.
253 */
254 public AbstractButton getChildButton(int index) {
255 return getButtonComponents()[index];
256 }
257
258 /**
259 * Retrieve the child button that represents this value.
260 */
261 public AbstractButton getChildButton(T value) {
262 final int index = values.indexOf(value);
263 return getButtonComponents()[index];
264 }
265
266 /**
267 * Get the number of child buttons.
268 */
269 public int getChildButtonCount() {
270 return getButtonComponents().length;
271 }
272
273 /**
274 * Adds an <code>ActionListener</code>.
275 * <p>
276 * The <code>ActionListener</code> will receive an <code>ActionEvent</code>
277 * when a selection has been made.
278 *
279 * @param l the <code>ActionListener</code> that is to be notified
280 * @see #setSelectedItem
281 */
282 public void addActionListener(ActionListener l) {
283 if (actionListeners == null) {
284 actionListeners = new ArrayList<ActionListener>();
285 }
286 actionListeners.add(l);
287 }
288
289 /**
290 * Removes an <code>ActionListener</code>.
291 *
292 * @param l
293 * the <code>ActionListener</code> to remove
294 */
295 public void removeActionListener(ActionListener l) {
296 if (actionListeners != null) {
297 actionListeners.remove(l);
298 }
299 }
300
301 /**
302 * Returns an array of all the <code>ActionListener</code>s added
303 * to this JRadioGroup with addActionListener().
304 *
305 * @return all of the <code>ActionListener</code>s added or an empty
306 * array if no listeners have been added
307 */
308 public ActionListener[] getActionListeners() {
309 if (actionListeners != null) {
310 return actionListeners.toArray(new ActionListener[0]);
311 }
312 return new ActionListener[0];
313 }
314
315 /**
316 * Notifies all listeners that have registered interest for notification on
317 * this event type.
318 *
319 * @param e
320 * the event to pass to the listeners
321 * @see EventListenerList
322 */
323 protected void fireActionEvent(ActionEvent e) {
324 if (actionListeners != null) {
325 for (int i = 0; i < actionListeners.size(); i++) {
326 ActionListener l = actionListeners.get(i);
327 l.actionPerformed(e);
328 }
329 }
330 }
331
332 /**
333 * Enable/disable all of the child buttons
334 *
335 * @see JComponent#setEnabled(boolean)
336 */
337 @Override
338 public void setEnabled(boolean enabled) {
339 super.setEnabled(enabled);
340 for (Enumeration<AbstractButton> en = buttonGroup.getElements(); en.hasMoreElements();) {
341 final AbstractButton button = en.nextElement();
342 /* We don't want to enable a button where the action does not
343 * permit it. */
344 if (enabled && button.getAction() != null
345 && !button.getAction().isEnabled()) {
346 // do nothing
347 } else {
348 button.setEnabled(enabled);
349 }
350 }
351 }
352
353 }