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