1 package net.sf.openrocket.gui.adaptors;
3 import java.awt.Component;
4 import java.awt.event.ActionEvent;
5 import java.beans.PropertyChangeListener;
6 import java.lang.reflect.InvocationTargetException;
7 import java.lang.reflect.Method;
8 import java.util.ArrayList;
9 import java.util.EventObject;
10 import java.util.List;
12 import javax.swing.AbstractAction;
14 import net.sf.openrocket.logging.LogHelper;
15 import net.sf.openrocket.startup.Application;
16 import net.sf.openrocket.util.BugException;
17 import net.sf.openrocket.util.ChangeSource;
18 import net.sf.openrocket.util.Invalidatable;
19 import net.sf.openrocket.util.Invalidator;
20 import net.sf.openrocket.util.MemoryManagement;
21 import net.sf.openrocket.util.Reflection;
22 import net.sf.openrocket.util.StateChangeListener;
26 * A class that adapts an isXXX/setXXX boolean variable. It functions as an Action suitable
27 * for usage in JCheckBox or JToggleButton. You can create a suitable button with
29 * check = new JCheckBox(new BooleanModel(component,"Value"))
30 * check.setText("Label");
32 * This will produce a button that uses isValue() and setValue(boolean) of the corresponding
35 * Additionally a number of component enabled states may be controlled by this class using
36 * the method {@link #addEnableComponent(Component, boolean)}.
38 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
40 public class BooleanModel extends AbstractAction implements StateChangeListener, Invalidatable {
41 private static final LogHelper log = Application.getLogger();
43 private final ChangeSource source;
44 private final String valueName;
46 /* Only used when referencing a ChangeSource! */
47 private final Method getMethod;
48 private final Method setMethod;
49 private final Method getEnabled;
51 /* Only used with internal boolean value! */
52 private boolean value;
55 private final List<Component> components = new ArrayList<Component>();
56 private final List<Boolean> componentEnableState = new ArrayList<Boolean>();
58 private String toString = null;
60 private int firing = 0;
62 private boolean oldValue;
63 private boolean oldEnabled;
65 private Invalidator invalidator = new Invalidator(this);
69 * Construct a BooleanModel that holds the boolean value within itself.
71 * @param initialValue the initial value of the boolean
73 public BooleanModel(boolean initialValue) {
74 this.valueName = null;
76 this.getMethod = null;
77 this.setMethod = null;
78 this.getEnabled = null;
80 this.value = initialValue;
82 oldValue = getValue();
83 oldEnabled = getIsEnabled();
85 this.setEnabled(oldEnabled);
86 this.putValue(SELECTED_KEY, oldValue);
91 * Construct a BooleanModel that references the boolean from a ChangeSource method.
93 * @param source the boolean source.
94 * @param valueName the name of the getter/setter method (without the get/is/set prefix)
96 public BooleanModel(ChangeSource source, String valueName) {
98 this.valueName = valueName;
100 Method getter = null, setter = null;
103 // Try get/is and set
105 getter = source.getClass().getMethod("is" + valueName);
106 } catch (NoSuchMethodException ignore) {
108 if (getter == null) {
110 getter = source.getClass().getMethod("get" + valueName);
111 } catch (NoSuchMethodException ignore) {
115 setter = source.getClass().getMethod("set" + valueName, boolean.class);
116 } catch (NoSuchMethodException ignore) {
119 if (getter == null || setter == null) {
120 throw new IllegalArgumentException("get/is methods for boolean '" + valueName +
121 "' not present in class " + source.getClass().getCanonicalName());
129 e = source.getClass().getMethod("is" + valueName + "Enabled");
130 } catch (NoSuchMethodException ignore) {
134 oldValue = getValue();
135 oldEnabled = getIsEnabled();
137 this.setEnabled(oldEnabled);
138 this.putValue(SELECTED_KEY, oldValue);
140 source.addChangeListener(this);
143 public boolean getValue() {
145 if (getMethod != null) {
148 return (Boolean) getMethod.invoke(source);
149 } catch (IllegalAccessException e) {
150 throw new BugException("getMethod execution error for source " + source, e);
151 } catch (InvocationTargetException e) {
152 throw Reflection.handleWrappedException(e);
157 // Use internal value
163 public void setValue(boolean b) {
165 log.debug("Setting value of " + this + " to " + b);
167 if (setMethod != null) {
169 setMethod.invoke(source, new Object[] { b });
170 } catch (IllegalAccessException e) {
171 throw new BugException("setMethod execution error for source " + source, e);
172 } catch (InvocationTargetException e) {
173 throw Reflection.handleWrappedException(e);
176 // Manually fire state change - normally the ChangeSource fires it
184 * Add a component the enabled status of which will be controlled by the value
185 * of this boolean. The <code>component</code> will be enabled exactly when
186 * the state of this model is equal to that of <code>enableState</code>.
188 * @param component the component to control.
189 * @param enableState the state in which the component should be enabled.
191 public void addEnableComponent(Component component, boolean enableState) {
193 components.add(component);
194 componentEnableState.add(enableState);
195 updateEnableStatus();
199 * Add a component which will be enabled when this boolean is <code>true</code>.
200 * This is equivalent to <code>booleanModel.addEnableComponent(component, true)</code>.
202 * @param component the component to control.
203 * @see #addEnableComponent(Component, boolean)
205 public void addEnableComponent(Component component) {
207 addEnableComponent(component, true);
210 private void updateEnableStatus() {
211 boolean state = getValue();
213 for (int i = 0; i < components.size(); i++) {
214 Component c = components.get(i);
215 boolean b = componentEnableState.get(i);
216 c.setEnabled(state == b);
222 private boolean getIsEnabled() {
223 if (getEnabled == null)
226 return (Boolean) getEnabled.invoke(source);
227 } catch (IllegalAccessException e) {
228 throw new BugException("getEnabled execution error for source " + source, e);
229 } catch (InvocationTargetException e) {
230 throw Reflection.handleWrappedException(e);
235 public void stateChanged(EventObject event) {
239 log.debug("Ignoring stateChanged of " + this + ", currently firing events");
243 boolean v = getValue();
244 boolean e = getIsEnabled();
246 log.debug("Value of " + this + " has changed to " + v + " oldValue=" + oldValue);
249 this.putValue(SELECTED_KEY, getValue());
250 // this.firePropertyChange(SELECTED_KEY, !v, v);
251 updateEnableStatus();
254 if (oldEnabled != e) {
255 log.debug("Enabled status of " + this + " has changed to " + e + " oldEnabled=" + oldEnabled);
263 public void actionPerformed(ActionEvent e) {
265 log.debug("Ignoring actionPerformed of " + this + ", currently firing events");
269 boolean v = (Boolean) this.getValue(SELECTED_KEY);
270 log.user("Value of " + this + " changed to " + v + " oldValue=" + oldValue);
274 oldValue = getValue();
276 this.putValue(SELECTED_KEY, oldValue);
277 this.setEnabled(getIsEnabled());
278 updateEnableStatus();
285 public void addPropertyChangeListener(PropertyChangeListener listener) {
287 super.addPropertyChangeListener(listener);
292 * Invalidates this model by removing all listeners and removing this from
293 * listening to the source. After invalidation no listeners can be added to this
294 * model and the value cannot be set.
297 public void invalidate() {
298 invalidator.invalidate();
300 PropertyChangeListener[] listeners = this.getPropertyChangeListeners();
301 if (listeners.length > 0) {
302 log.warn("Invalidating " + this + " while still having listeners " + listeners);
303 for (PropertyChangeListener l : listeners) {
304 this.removePropertyChangeListener(l);
307 if (source != null) {
308 source.removeChangeListener(this);
310 MemoryManagement.collectable(this);
314 private void checkState(boolean error) {
315 invalidator.check(error);
321 public String toString() {
322 if (toString == null) {
323 if (source != null) {
324 toString = "BooleanModel[" + source.getClass().getSimpleName() + ":" + valueName + "]";
326 toString = "BooleanModel[internal value]";