bug fix + more logging
[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.lang.reflect.InvocationTargetException;
6 import java.lang.reflect.Method;
7 import java.util.ArrayList;
8 import java.util.List;
9
10 import javax.swing.AbstractAction;
11 import javax.swing.event.ChangeEvent;
12 import javax.swing.event.ChangeListener;
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.Reflection;
19
20
21 /**
22  * A class that adapts an isXXX/setXXX boolean variable.  It functions as an Action suitable
23  * for usage in JCheckBox or JToggleButton.  You can create a suitable button with
24  * <code>
25  *   check = new JCheckBox(new BooleanModel(component,"Value"))
26  *   check.setText("Label");
27  * </code>
28  * This will produce a button that uses isValue() and setValue(boolean) of the corresponding
29  * component.
30  * <p>
31  * Additionally a number of component enabled states may be controlled by this class using
32  * the method {@link #addEnableComponent(Component, boolean)}.
33  * 
34  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
35  */
36
37 public class BooleanModel extends AbstractAction implements ChangeListener {
38         private static final LogHelper log = Application.getLogger();
39         
40         private final ChangeSource source;
41         private final String valueName;
42         
43         private final Method getMethod;
44         private final Method setMethod;
45         private final Method getEnabled;
46         
47         private final List<Component> components = new ArrayList<Component>();
48         private final List<Boolean> componentEnableState = new ArrayList<Boolean>();
49         
50         private String toString = null;
51         
52         private int firing = 0;
53         
54         private boolean oldValue;
55         private boolean oldEnabled;
56         
57         public BooleanModel(ChangeSource source, String valueName) {
58                 this.source = source;
59                 this.valueName = valueName;
60                 
61                 Method getter = null, setter = null;
62                 
63
64                 // Try get/is and set
65                 try {
66                         getter = source.getClass().getMethod("is" + valueName);
67                 } catch (NoSuchMethodException ignore) {
68                 }
69                 if (getter == null) {
70                         try {
71                                 getter = source.getClass().getMethod("get" + valueName);
72                         } catch (NoSuchMethodException ignore) {
73                         }
74                 }
75                 try {
76                         setter = source.getClass().getMethod("set" + valueName, boolean.class);
77                 } catch (NoSuchMethodException ignore) {
78                 }
79                 
80                 if (getter == null || setter == null) {
81                         throw new IllegalArgumentException("get/is methods for boolean '" + valueName +
82                                         "' not present in class " + source.getClass().getCanonicalName());
83                 }
84                 
85                 getMethod = getter;
86                 setMethod = setter;
87                 
88                 Method e = null;
89                 try {
90                         e = source.getClass().getMethod("is" + valueName + "Enabled");
91                 } catch (NoSuchMethodException ignore) {
92                 }
93                 getEnabled = e;
94                 
95                 oldValue = getValue();
96                 oldEnabled = getIsEnabled();
97                 
98                 this.setEnabled(oldEnabled);
99                 this.putValue(SELECTED_KEY, oldValue);
100                 
101                 source.addChangeListener(this);
102         }
103         
104         public boolean getValue() {
105                 try {
106                         return (Boolean) getMethod.invoke(source);
107                 } catch (IllegalAccessException e) {
108                         throw new BugException("getMethod execution error for source " + source, e);
109                 } catch (InvocationTargetException e) {
110                         throw Reflection.handleWrappedException(e);
111                 }
112         }
113         
114         public void setValue(boolean b) {
115                 log.debug("Setting value of " + this + " to " + b);
116                 try {
117                         setMethod.invoke(source, new Object[] { (Boolean) b });
118                 } catch (IllegalAccessException e) {
119                         throw new BugException("setMethod execution error for source " + source, e);
120                 } catch (InvocationTargetException e) {
121                         throw Reflection.handleWrappedException(e);
122                 }
123         }
124         
125         
126         /**
127          * Add a component the enabled status of which will be controlled by the value
128          * of this boolean.  The <code>component</code> will be enabled exactly when
129          * the state of this model is equal to that of <code>enableState</code>.
130          * 
131          * @param component             the component to control.
132          * @param enableState   the state in which the component should be enabled.
133          */
134         public void addEnableComponent(Component component, boolean enableState) {
135                 components.add(component);
136                 componentEnableState.add(enableState);
137                 updateEnableStatus();
138         }
139         
140         /**
141          * Add a component which will be enabled when this boolean is <code>true</code>.
142          * This is equivalent to <code>booleanModel.addEnableComponent(component, true)</code>.
143          * 
144          * @param component             the component to control.
145          * @see #addEnableComponent(Component, boolean)
146          */
147         public void addEnableComponent(Component component) {
148                 addEnableComponent(component, true);
149         }
150         
151         private void updateEnableStatus() {
152                 boolean state = getValue();
153                 
154                 for (int i = 0; i < components.size(); i++) {
155                         Component c = components.get(i);
156                         boolean b = componentEnableState.get(i);
157                         c.setEnabled(state == b);
158                 }
159         }
160         
161         
162
163         private boolean getIsEnabled() {
164                 if (getEnabled == null)
165                         return true;
166                 try {
167                         return (Boolean) getEnabled.invoke(source);
168                 } catch (IllegalAccessException e) {
169                         throw new BugException("getEnabled execution error for source " + source, e);
170                 } catch (InvocationTargetException e) {
171                         throw Reflection.handleWrappedException(e);
172                 }
173         }
174         
175         @Override
176         public void stateChanged(ChangeEvent event) {
177                 if (firing > 0) {
178                         log.debug("Ignoring stateChanged of " + this + ", currently firing events");
179                         return;
180                 }
181                 
182                 boolean v = getValue();
183                 boolean e = getIsEnabled();
184                 if (oldValue != v) {
185                         log.debug("Value of " + this + " has changed to " + v + " oldValue=" + oldValue);
186                         oldValue = v;
187                         firing++;
188                         this.putValue(SELECTED_KEY, getValue());
189                         //                      this.firePropertyChange(SELECTED_KEY, !v, v);
190                         updateEnableStatus();
191                         firing--;
192                 }
193                 if (oldEnabled != e) {
194                         log.debug("Enabled status of " + this + " has changed to " + e + " oldEnabled=" + oldEnabled);
195                         oldEnabled = e;
196                         setEnabled(e);
197                 }
198         }
199         
200         
201         @Override
202         public void actionPerformed(ActionEvent e) {
203                 if (firing > 0) {
204                         log.debug("Ignoring actionPerformed of " + this + ", currently firing events");
205                         return;
206                 }
207                 
208                 boolean v = (Boolean) this.getValue(SELECTED_KEY);
209                 log.user("Value of " + this + " changed to " + v + " oldValue=" + oldValue);
210                 if (v != oldValue) {
211                         firing++;
212                         setValue(v);
213                         oldValue = getValue();
214                         // Update all states
215                         this.putValue(SELECTED_KEY, oldValue);
216                         this.setEnabled(getIsEnabled());
217                         updateEnableStatus();
218                         firing--;
219                 }
220         }
221         
222         @Override
223         public String toString() {
224                 if (toString == null) {
225                         toString = "BooleanModel[" + source.getClass().getSimpleName() + ":" + valueName + "]";
226                 }
227                 return toString;
228         }
229 }