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 }