001    /* JAPI - (Yet anothr (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:Christian.Hujer@itcqis.com">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 java.lang.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 java.lang.String}.
049         */
050        public static final String REFLECTION_PROPERTY_NAME = "ReflectionPropertyName";
051    
052        /** Serial Version. */
053        private static final long serialVersionUID = 1L;
054    
055        /** The selected state.
056         * @serial include
057         */
058        private boolean selected;
059    
060        /** The buttons created. */
061        private final transient List<WeakReference<AbstractButton>> buttons = new ArrayList<WeakReference<AbstractButton>>();
062    
063        /** Returns the state of the action.
064         * @return selected state of this action
065         */
066        public boolean isSelected() {
067            return selected;
068        }
069    
070        /** {@inheritDoc} */
071        public void actionPerformed(final ActionEvent e) {
072            if (!isEnabled()) {
073                return;
074            }
075            final Object instance = getValue(REFLECTION_TARGET);
076            String property = (String) getValue(REFLECTION_PROPERTY_NAME);
077            property = Character.toUpperCase(property.charAt(0)) + property.substring(1);
078            final String getterName = "is" + property;
079            final String setterName = "set" + property;
080            try {
081                final Method getter = instance.getClass().getMethod(getterName);
082                final Method setter = instance.getClass().getMethod(setterName, boolean.class);
083                setter.invoke(instance, !(Boolean) getter.invoke(instance));
084                setSelected((Boolean) getter.invoke(instance));
085            } catch (final NoSuchMethodException ex) {
086                assert false : ex;
087            } catch (final IllegalAccessException ex) {
088                assert false : ex;
089            } catch (final InvocationTargetException ex) {
090                throw new RuntimeException(ex.getCause());
091            }
092        }
093    
094        /** {@inheritDoc} */
095        @Override protected Object clone() throws CloneNotSupportedException {
096            return super.clone();
097        }
098    
099        /** Create a JCheckBox for this action.
100         * @return JCheckBox for this action
101         */
102        public JCheckBox createCheckBox() {
103            final JCheckBox ret = new JCheckBox(this);
104            buttons.add(new WeakReference<AbstractButton>(ret));
105            return ret;
106        }
107    
108        /** Create a JCheckBoxMenuItem.
109         * @return JCheckBoxMenuItem for this action
110         */
111        public JCheckBoxMenuItem createCheckBoxMenuItem() {
112            final JCheckBoxMenuItem ret = new JCheckBoxMenuItem(this);
113            buttons.add(new WeakReference<AbstractButton>(ret));
114            return ret;
115        }
116    
117        /** {@inheritDoc}
118         * This implementation checks the type of <var>newValue</var> if the <var>key</var> is {@link #REFLECTION_TARGET} or {@link
119         * #REFLECTION_PROPERTY_NAME}, so you'll know of errors quite soon.
120         * @throws IllegalArgumentException if <var>newValue</var> is of the wrong type
121         */
122        @Override public void putValue(final String key, final Object newValue) throws IllegalArgumentException {
123            if (REFLECTION_PROPERTY_NAME.equals(key)) {
124                if (newValue != null && !(newValue instanceof String)) {
125                    throw new IllegalArgumentException("Value for key REFLECTION_PROPERTY_NAME must be of type " + String.class.getName() + " but was " + newValue.getClass().getName());
126                }
127            }
128            super.putValue(key, newValue);
129        }
130    
131        /** Update the selected state.
132         * @param selected new selected state
133         */
134        public void setSelected(final boolean selected) {
135            this.selected = selected;
136            for (final Iterator<WeakReference<AbstractButton>> it = buttons.iterator(); it.hasNext();) {
137                final WeakReference<AbstractButton> ref = it.next();
138                final AbstractButton button = ref.get();
139                if (button == null) {
140                    it.remove();
141                } else {
142                    button.setSelected(selected);
143                }
144            }
145        }
146    
147    } // class ToggleAction