001 /* JAPI - (Yet another (hopefully) useful) Java API
002 *
003 * Copyright (C) 2004-2006 Christian Hujer
004 *
005 * This program is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU General Public License as
007 * published by the Free Software Foundation; either version 2 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful, but
011 * WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013 * General Public License for more details.
014 *
015 * You should have received a copy of the GNU General Public License
016 * along with this program; if not, write to the Free Software
017 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
018 * 02111-1307, USA.
019 */
020
021 package net.sf.japi.swing;
022
023 import java.awt.event.ActionEvent;
024 import java.lang.ref.WeakReference;
025 import java.lang.reflect.InvocationTargetException;
026 import java.lang.reflect.Method;
027 import java.util.ArrayList;
028 import java.util.Iterator;
029 import java.util.List;
030 import javax.swing.AbstractAction;
031 import javax.swing.AbstractButton;
032 import javax.swing.JCheckBox;
033 import javax.swing.JCheckBoxMenuItem;
034
035 /** The ToggleAction works similar as an ReflectionAction.
036 * But it keeps track of the components.
037 * Be sure to use its factory methodsA
038 * @author <a href="mailto:chris@riedquat.de">Christian Hujer</a>
039 */
040 public final class ToggleAction extends AbstractAction {
041
042 /** The key used for storing the target object to invoke the methods on.
043 * Value Type: {@link Object}.
044 */
045 public static final String REFLECTION_TARGET = "ReflectionTarget";
046
047 /** The key used for storing the target object's boolean property name to find the methods
048 * Value Type: {@link String}.
049 */
050 public static final String REFLECTION_PROPERTY_NAME = "ReflectionPropertyName";
051
052 /** Serial Version. */
053 // Work around bug in IntelliJ IDEA
054 @SuppressWarnings({"AnalyzingVariableNaming"})
055 private static final long serialVersionUID = 1L;
056
057 /** The selected state.
058 * @serial include
059 */
060 private boolean selected;
061
062 /** The buttons created. */
063 private final transient List<WeakReference<AbstractButton>> buttons = new ArrayList<WeakReference<AbstractButton>>();
064
065 /** Returns the state of the action.
066 * @return selected state of this action
067 */
068 public boolean isSelected() {
069 return selected;
070 }
071
072 /** {@inheritDoc} */
073 public void actionPerformed(final ActionEvent e) {
074 if (!isEnabled()) {
075 return;
076 }
077 final Object instance = getValue(REFLECTION_TARGET);
078 String property = (String) getValue(REFLECTION_PROPERTY_NAME);
079 property = Character.toUpperCase(property.charAt(0)) + property.substring(1);
080 final String getterName = "is" + property;
081 final String setterName = "set" + property;
082 try {
083 final Method getter = instance.getClass().getMethod(getterName);
084 final Method setter = instance.getClass().getMethod(setterName, boolean.class);
085 setter.invoke(instance, !(Boolean) getter.invoke(instance));
086 setSelected((Boolean) getter.invoke(instance));
087 } catch (final NoSuchMethodException ex) {
088 assert false : ex;
089 } catch (final IllegalAccessException ex) {
090 assert false : ex;
091 } catch (final InvocationTargetException ex) {
092 // XXX workaround bug in IntelliJ IDEA (ex is NOT ignored)
093 //noinspection ThrowInsideCatchBlockWhichIgnoresCaughtException
094 throw new RuntimeException(ex.getCause());
095 }
096 }
097
098 /** {@inheritDoc} */
099 @Override protected Object clone() throws CloneNotSupportedException {
100 return super.clone();
101 }
102
103 /** Create a JCheckBox for this action.
104 * @return JCheckBox for this action
105 */
106 public JCheckBox createCheckBox() {
107 final JCheckBox ret = new JCheckBox(this);
108 buttons.add(new WeakReference<AbstractButton>(ret));
109 return ret;
110 }
111
112 /** Create a JCheckBoxMenuItem.
113 * @return JCheckBoxMenuItem for this action
114 */
115 public JCheckBoxMenuItem createCheckBoxMenuItem() {
116 final JCheckBoxMenuItem ret = new JCheckBoxMenuItem(this);
117 buttons.add(new WeakReference<AbstractButton>(ret));
118 return ret;
119 }
120
121 /** {@inheritDoc}
122 * This implementation checks the type of <var>newValue</var> if the <var>key</var> is {@link #REFLECTION_TARGET} or {@link
123 * #REFLECTION_PROPERTY_NAME}, so you'll know of errors quite soon.
124 * @throws IllegalArgumentException if <var>newValue</var> is of the wrong type
125 */
126 @Override public void putValue(final String key, final Object newValue) throws IllegalArgumentException {
127 if (REFLECTION_PROPERTY_NAME.equals(key)) {
128 if (newValue != null && !(newValue instanceof String)) {
129 throw new IllegalArgumentException("Value for key REFLECTION_PROPERTY_NAME must be of type " + String.class.getName() + " but was " + newValue.getClass().getName());
130 }
131 }
132 super.putValue(key, newValue);
133 }
134
135 /** Update the selected state.
136 * @param selected new selected state
137 */
138 public void setSelected(final boolean selected) {
139 this.selected = selected;
140 //noinspection ForLoopWithMissingComponent
141 for (final Iterator<WeakReference<AbstractButton>> it = buttons.iterator(); it.hasNext();) {
142 final WeakReference<AbstractButton> ref = it.next();
143 final AbstractButton button = ref.get();
144 if (button == null) {
145 it.remove();
146 } else {
147 button.setSelected(selected);
148 }
149 }
150 }
151
152 } // class ToggleAction