a88f4b892d8a56aadbb779781ff41b60710e114f
[debian/openrocket] / src / net / sf / openrocket / gui / adaptors / BooleanModel.java
1 package net.sf.openrocket.gui.adaptors;
2
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.List;
10
11 import javax.swing.AbstractAction;
12 import javax.swing.event.ChangeEvent;
13 import javax.swing.event.ChangeListener;
14
15 import net.sf.openrocket.logging.LogHelper;
16 import net.sf.openrocket.startup.Application;
17 import net.sf.openrocket.util.BugException;
18 import net.sf.openrocket.util.ChangeSource;
19 import net.sf.openrocket.util.Invalidatable;
20 import net.sf.openrocket.util.Invalidator;
21 import net.sf.openrocket.util.MemoryManagement;
22 import net.sf.openrocket.util.Reflection;
23
24
25 /**
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
28  * <code>
29  *   check = new JCheckBox(new BooleanModel(component,"Value"))
30  *   check.setText("Label");
31  * </code>
32  * This will produce a button that uses isValue() and setValue(boolean) of the corresponding
33  * component.
34  * <p>
35  * Additionally a number of component enabled states may be controlled by this class using
36  * the method {@link #addEnableComponent(Component, boolean)}.
37  * 
38  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
39  */
40
41 public class BooleanModel extends AbstractAction implements ChangeListener, Invalidatable {
42         private static final LogHelper log = Application.getLogger();
43         
44         private final ChangeSource source;
45         private final String valueName;
46         
47         private final Method getMethod;
48         private final Method setMethod;
49         private final Method getEnabled;
50         
51         private final List<Component> components = new ArrayList<Component>();
52         private final List<Boolean> componentEnableState = new ArrayList<Boolean>();
53         
54         private String toString = null;
55         
56         private int firing = 0;
57         
58         private boolean oldValue;
59         private boolean oldEnabled;
60         
61         private Invalidator invalidator = new Invalidator(this);
62         
63         
64         public BooleanModel(ChangeSource source, String valueName) {
65                 this.source = source;
66                 this.valueName = valueName;
67                 
68                 Method getter = null, setter = null;
69                 
70
71                 // Try get/is and set
72                 try {
73                         getter = source.getClass().getMethod("is" + valueName);
74                 } catch (NoSuchMethodException ignore) {
75                 }
76                 if (getter == null) {
77                         try {
78                                 getter = source.getClass().getMethod("get" + valueName);
79                         } catch (NoSuchMethodException ignore) {
80                         }
81                 }
82                 try {
83                         setter = source.getClass().getMethod("set" + valueName, boolean.class);
84                 } catch (NoSuchMethodException ignore) {
85                 }
86                 
87                 if (getter == null || setter == null) {
88                         throw new IllegalArgumentException("get/is methods for boolean '" + valueName +
89                                         "' not present in class " + source.getClass().getCanonicalName());
90                 }
91                 
92                 getMethod = getter;
93                 setMethod = setter;
94                 
95                 Method e = null;
96                 try {
97                         e = source.getClass().getMethod("is" + valueName + "Enabled");
98                 } catch (NoSuchMethodException ignore) {
99                 }
100                 getEnabled = e;
101                 
102                 oldValue = getValue();
103                 oldEnabled = getIsEnabled();
104                 
105                 this.setEnabled(oldEnabled);
106                 this.putValue(SELECTED_KEY, oldValue);
107                 
108                 source.addChangeListener(this);
109         }
110         
111         public boolean getValue() {
112                 try {
113                         return (Boolean) getMethod.invoke(source);
114                 } catch (IllegalAccessException e) {
115                         throw new BugException("getMethod execution error for source " + source, e);
116                 } catch (InvocationTargetException e) {
117                         throw Reflection.handleWrappedException(e);
118                 }
119         }
120         
121         public void setValue(boolean b) {
122                 checkState(true);
123                 log.debug("Setting value of " + this + " to " + b);
124                 try {
125                         setMethod.invoke(source, new Object[] { b });
126                 } catch (IllegalAccessException e) {
127                         throw new BugException("setMethod execution error for source " + source, e);
128                 } catch (InvocationTargetException e) {
129                         throw Reflection.handleWrappedException(e);
130                 }
131         }
132         
133         
134         /**
135          * Add a component the enabled status of which will be controlled by the value
136          * of this boolean.  The <code>component</code> will be enabled exactly when
137          * the state of this model is equal to that of <code>enableState</code>.
138          * 
139          * @param component             the component to control.
140          * @param enableState   the state in which the component should be enabled.
141          */
142         public void addEnableComponent(Component component, boolean enableState) {
143                 checkState(true);
144                 components.add(component);
145                 componentEnableState.add(enableState);
146                 updateEnableStatus();
147         }
148         
149         /**
150          * Add a component which will be enabled when this boolean is <code>true</code>.
151          * This is equivalent to <code>booleanModel.addEnableComponent(component, true)</code>.
152          * 
153          * @param component             the component to control.
154          * @see #addEnableComponent(Component, boolean)
155          */
156         public void addEnableComponent(Component component) {
157                 checkState(true);
158                 addEnableComponent(component, true);
159         }
160         
161         private void updateEnableStatus() {
162                 boolean state = getValue();
163                 
164                 for (int i = 0; i < components.size(); i++) {
165                         Component c = components.get(i);
166                         boolean b = componentEnableState.get(i);
167                         c.setEnabled(state == b);
168                 }
169         }
170         
171         
172
173         private boolean getIsEnabled() {
174                 if (getEnabled == null)
175                         return true;
176                 try {
177                         return (Boolean) getEnabled.invoke(source);
178                 } catch (IllegalAccessException e) {
179                         throw new BugException("getEnabled execution error for source " + source, e);
180                 } catch (InvocationTargetException e) {
181                         throw Reflection.handleWrappedException(e);
182                 }
183         }
184         
185         @Override
186         public void stateChanged(ChangeEvent event) {
187                 checkState(true);
188                 
189                 if (firing > 0) {
190                         log.debug("Ignoring stateChanged of " + this + ", currently firing events");
191                         return;
192                 }
193                 
194                 boolean v = getValue();
195                 boolean e = getIsEnabled();
196                 if (oldValue != v) {
197                         log.debug("Value of " + this + " has changed to " + v + " oldValue=" + oldValue);
198                         oldValue = v;
199                         firing++;
200                         this.putValue(SELECTED_KEY, getValue());
201                         //                      this.firePropertyChange(SELECTED_KEY, !v, v);
202                         updateEnableStatus();
203                         firing--;
204                 }
205                 if (oldEnabled != e) {
206                         log.debug("Enabled status of " + this + " has changed to " + e + " oldEnabled=" + oldEnabled);
207                         oldEnabled = e;
208                         setEnabled(e);
209                 }
210         }
211         
212         
213         @Override
214         public void actionPerformed(ActionEvent e) {
215                 if (firing > 0) {
216                         log.debug("Ignoring actionPerformed of " + this + ", currently firing events");
217                         return;
218                 }
219                 
220                 boolean v = (Boolean) this.getValue(SELECTED_KEY);
221                 log.user("Value of " + this + " changed to " + v + " oldValue=" + oldValue);
222                 if (v != oldValue) {
223                         firing++;
224                         setValue(v);
225                         oldValue = getValue();
226                         // Update all states
227                         this.putValue(SELECTED_KEY, oldValue);
228                         this.setEnabled(getIsEnabled());
229                         updateEnableStatus();
230                         firing--;
231                 }
232         }
233         
234         
235         @Override
236         public void addPropertyChangeListener(PropertyChangeListener listener) {
237                 checkState(true);
238                 super.addPropertyChangeListener(listener);
239         }
240         
241         
242         /**
243          * Invalidates this model by removing all listeners and removing this from
244          * listening to the source.  After invalidation no listeners can be added to this
245          * model and the value cannot be set.
246          */
247         @Override
248         public void invalidate() {
249                 invalidator.invalidate();
250                 
251                 PropertyChangeListener[] listeners = this.getPropertyChangeListeners();
252                 if (listeners.length > 0) {
253                         log.warn("Invalidating " + this + " while still having listeners " + listeners);
254                         for (PropertyChangeListener l : listeners) {
255                                 this.removePropertyChangeListener(l);
256                         }
257                 }
258                 if (source != null) {
259                         source.removeChangeListener(this);
260                 }
261                 MemoryManagement.collectable(this);
262         }
263         
264         
265         private void checkState(boolean error) {
266                 invalidator.check(error);
267         }
268         
269         
270
271         @Override
272         public String toString() {
273                 if (toString == null) {
274                         toString = "BooleanModel[" + source.getClass().getSimpleName() + ":" + valueName + "]";
275                 }
276                 return toString;
277         }
278 }