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