create changelog entry
[debian/openrocket] / core / 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.EventObject;
10 import java.util.List;
11
12 import javax.swing.AbstractAction;
13
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;
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 public class BooleanModel extends AbstractAction implements StateChangeListener, Invalidatable {
41         private static final LogHelper log = Application.getLogger();
42         
43         private final ChangeSource source;
44         private final String valueName;
45         
46         /* Only used when referencing a ChangeSource! */
47         private final Method getMethod;
48         private final Method setMethod;
49         private final Method getEnabled;
50         
51         /* Only used with internal boolean value! */
52         private boolean value;
53         
54
55         private final List<Component> components = new ArrayList<Component>();
56         private final List<Boolean> componentEnableState = new ArrayList<Boolean>();
57         
58         private String toString = null;
59         
60         private int firing = 0;
61         
62         private boolean oldValue;
63         private boolean oldEnabled;
64         
65         private Invalidator invalidator = new Invalidator(this);
66         
67         
68         /**
69          * Construct a BooleanModel that holds the boolean value within itself.
70          * 
71          * @param initialValue  the initial value of the boolean
72          */
73         public BooleanModel(boolean initialValue) {
74                 this.valueName = null;
75                 this.source = null;
76                 this.getMethod = null;
77                 this.setMethod = null;
78                 this.getEnabled = null;
79                 
80                 this.value = initialValue;
81                 
82                 oldValue = getValue();
83                 oldEnabled = getIsEnabled();
84                 
85                 this.setEnabled(oldEnabled);
86                 this.putValue(SELECTED_KEY, oldValue);
87                 
88         }
89         
90         /**
91          * Construct a BooleanModel that references the boolean from a ChangeSource method.
92          * 
93          * @param source                the boolean source.
94          * @param valueName             the name of the getter/setter method (without the get/is/set prefix)
95          */
96         public BooleanModel(ChangeSource source, String valueName) {
97                 this.source = source;
98                 this.valueName = valueName;
99                 
100                 Method getter = null, setter = null;
101                 
102
103                 // Try get/is and set
104                 try {
105                         getter = source.getClass().getMethod("is" + valueName);
106                 } catch (NoSuchMethodException ignore) {
107                 }
108                 if (getter == null) {
109                         try {
110                                 getter = source.getClass().getMethod("get" + valueName);
111                         } catch (NoSuchMethodException ignore) {
112                         }
113                 }
114                 try {
115                         setter = source.getClass().getMethod("set" + valueName, boolean.class);
116                 } catch (NoSuchMethodException ignore) {
117                 }
118                 
119                 if (getter == null || setter == null) {
120                         throw new IllegalArgumentException("get/is methods for boolean '" + valueName +
121                                         "' not present in class " + source.getClass().getCanonicalName());
122                 }
123                 
124                 getMethod = getter;
125                 setMethod = setter;
126                 
127                 Method e = null;
128                 try {
129                         e = source.getClass().getMethod("is" + valueName + "Enabled");
130                 } catch (NoSuchMethodException ignore) {
131                 }
132                 getEnabled = e;
133                 
134                 oldValue = getValue();
135                 oldEnabled = getIsEnabled();
136                 
137                 this.setEnabled(oldEnabled);
138                 this.putValue(SELECTED_KEY, oldValue);
139                 
140                 source.addChangeListener(this);
141         }
142         
143         public boolean getValue() {
144                 
145                 if (getMethod != null) {
146                         
147                         try {
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);
153                         }
154                         
155                 } else {
156                         
157                         // Use internal value
158                         return value;
159                         
160                 }
161         }
162         
163         public void setValue(boolean b) {
164                 checkState(true);
165                 log.debug("Setting value of " + this + " to " + b);
166                 
167                 if (setMethod != null) {
168                         try {
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);
174                         }
175                 } else {
176                         // Manually fire state change - normally the ChangeSource fires it
177                         value = b;
178                         stateChanged(null);
179                 }
180         }
181         
182         
183         /**
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>.
187          * 
188          * @param component             the component to control.
189          * @param enableState   the state in which the component should be enabled.
190          */
191         public void addEnableComponent(Component component, boolean enableState) {
192                 checkState(true);
193                 components.add(component);
194                 componentEnableState.add(enableState);
195                 updateEnableStatus();
196         }
197         
198         /**
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>.
201          * 
202          * @param component             the component to control.
203          * @see #addEnableComponent(Component, boolean)
204          */
205         public void addEnableComponent(Component component) {
206                 checkState(true);
207                 addEnableComponent(component, true);
208         }
209         
210         private void updateEnableStatus() {
211                 boolean state = getValue();
212                 
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);
217                 }
218         }
219         
220         
221
222         private boolean getIsEnabled() {
223                 if (getEnabled == null)
224                         return true;
225                 try {
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);
231                 }
232         }
233         
234         @Override
235         public void stateChanged(EventObject event) {
236                 checkState(true);
237                 
238                 if (firing > 0) {
239                         log.debug("Ignoring stateChanged of " + this + ", currently firing events");
240                         return;
241                 }
242                 
243                 boolean v = getValue();
244                 boolean e = getIsEnabled();
245                 if (oldValue != v) {
246                         log.debug("Value of " + this + " has changed to " + v + " oldValue=" + oldValue);
247                         oldValue = v;
248                         firing++;
249                         this.putValue(SELECTED_KEY, getValue());
250                         //                      this.firePropertyChange(SELECTED_KEY, !v, v);
251                         updateEnableStatus();
252                         firing--;
253                 }
254                 if (oldEnabled != e) {
255                         log.debug("Enabled status of " + this + " has changed to " + e + " oldEnabled=" + oldEnabled);
256                         oldEnabled = e;
257                         setEnabled(e);
258                 }
259         }
260         
261         
262         @Override
263         public void actionPerformed(ActionEvent e) {
264                 if (firing > 0) {
265                         log.debug("Ignoring actionPerformed of " + this + ", currently firing events");
266                         return;
267                 }
268                 
269                 boolean v = (Boolean) this.getValue(SELECTED_KEY);
270                 log.user("Value of " + this + " changed to " + v + " oldValue=" + oldValue);
271                 if (v != oldValue) {
272                         firing++;
273                         setValue(v);
274                         oldValue = getValue();
275                         // Update all states
276                         this.putValue(SELECTED_KEY, oldValue);
277                         this.setEnabled(getIsEnabled());
278                         updateEnableStatus();
279                         firing--;
280                 }
281         }
282         
283         
284         @Override
285         public void addPropertyChangeListener(PropertyChangeListener listener) {
286                 checkState(true);
287                 super.addPropertyChangeListener(listener);
288         }
289         
290         
291         /**
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.
295          */
296         @Override
297         public void invalidate() {
298                 invalidator.invalidate();
299                 
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);
305                         }
306                 }
307                 if (source != null) {
308                         source.removeChangeListener(this);
309                 }
310                 MemoryManagement.collectable(this);
311         }
312         
313         
314         private void checkState(boolean error) {
315                 invalidator.check(error);
316         }
317         
318         
319
320         @Override
321         public String toString() {
322                 if (toString == null) {
323                         if (source != null) {
324                                 toString = "BooleanModel[" + source.getClass().getSimpleName() + ":" + valueName + "]";
325                         } else {
326                                 toString = "BooleanModel[internal value]";
327                         }
328                 }
329                 return toString;
330         }
331 }