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