create changelog entry
[debian/openrocket] / core / src / net / sf / openrocket / gui / adaptors / IntegerModel.java
1 package net.sf.openrocket.gui.adaptors;
2
3 import java.lang.reflect.InvocationTargetException;
4 import java.lang.reflect.Method;
5 import java.util.ArrayList;
6 import java.util.EventListener;
7 import java.util.EventObject;
8
9 import javax.swing.SpinnerModel;
10 import javax.swing.SpinnerNumberModel;
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 import net.sf.openrocket.util.StateChangeListener;
20
21
22 public class IntegerModel implements StateChangeListener {
23         private static final LogHelper log = Application.getLogger();
24         
25         
26         //////////// JSpinner Model ////////////
27         
28         private class IntegerSpinnerModel extends SpinnerNumberModel {
29                 @Override
30                 public Object getValue() {
31                         return IntegerModel.this.getValue();
32                 }
33                 
34                 @Override
35                 public void setValue(Object value) {
36                         if (firing > 0) {
37                                 // Ignore, if called when model is sending events
38                                 log.verbose("Ignoring call to SpinnerModel setValue for " + IntegerModel.this.toString() +
39                                                 " value=" + value + ", currently firing events");
40                                 return;
41                                 
42                         }
43                         Number num = (Number) value;
44                         int newValue = num.intValue();
45                         log.user("SpinnerModel setValue called for " + IntegerModel.this.toString() + " newValue=" + newValue);
46                         IntegerModel.this.setValue(newValue);
47                 }
48                 
49                 @Override
50                 public Object getNextValue() {
51                         int d = IntegerModel.this.getValue();
52                         if (d >= maxValue)
53                                 return null;
54                         return (d + 1);
55                 }
56                 
57                 @Override
58                 public Object getPreviousValue() {
59                         int d = IntegerModel.this.getValue();
60                         if (d <= minValue)
61                                 return null;
62                         return (d - 1);
63                 }
64                 
65                 @Override
66                 public void addChangeListener(ChangeListener l) {
67                         IntegerModel.this.addChangeListener(l);
68                 }
69                 
70                 @Override
71                 public void removeChangeListener(ChangeListener l) {
72                         IntegerModel.this.removeChangeListener(l);
73                 }
74         }
75         
76         /**
77          * Returns a new SpinnerModel with the same base as the DoubleModel.
78          * The values given to the JSpinner are in the currently selected units.
79          * 
80          * @return  A compatibility layer for a SpinnerModel.
81          */
82         public SpinnerModel getSpinnerModel() {
83                 return new IntegerSpinnerModel();
84         }
85         
86         
87
88
89         ////////////  Main model  /////////////
90         
91         /*
92          * The main model handles all values in SI units, i.e. no conversion is made within the model.
93          */
94
95         private final ChangeSource source;
96         private final String valueName;
97         
98         private final Method getMethod;
99         private final Method setMethod;
100         
101         private final ArrayList<EventListener> listeners = new ArrayList<EventListener>();
102         
103         private final int minValue;
104         private final int maxValue;
105         
106         private String toString = null;
107         
108
109         private int firing = 0; //  >0 when model itself is sending events
110         
111
112         // Used to differentiate changes in valueName and other changes in the source:
113         private int lastValue = 0;
114         
115         
116
117         /**
118          * Generates a new DoubleModel that changes the values of the specified source.
119          * The double value is read and written using the methods "get"/"set" + valueName.
120          *  
121          * @param source Component whose parameter to use.
122          * @param valueName Name of methods used to get/set the parameter.
123          * @param min Minimum value allowed (in SI units)
124          * @param max Maximum value allowed (in SI units)
125          */
126         public IntegerModel(ChangeSource source, String valueName, int min, int max) {
127                 this.source = source;
128                 this.valueName = valueName;
129                 
130                 this.minValue = min;
131                 this.maxValue = max;
132                 
133                 try {
134                         getMethod = source.getClass().getMethod("get" + valueName);
135                         setMethod = source.getClass().getMethod("set" + valueName, int.class);
136                 } catch (NoSuchMethodException e) {
137                         throw new IllegalArgumentException("get/set methods for value '" + valueName +
138                                         "' not present in class " + source.getClass().getCanonicalName());
139                 }
140         }
141         
142         public IntegerModel(ChangeSource source, String valueName, int min) {
143                 this(source, valueName, min, Integer.MAX_VALUE);
144         }
145         
146         public IntegerModel(ChangeSource source, String valueName) {
147                 this(source, valueName, Integer.MIN_VALUE, Integer.MAX_VALUE);
148         }
149         
150         
151
152
153         /**
154          * Returns the value of the variable.
155          */
156         public int getValue() {
157                 try {
158                         return (Integer) getMethod.invoke(source);
159                 } catch (IllegalArgumentException e) {
160                         throw new BugException(e);
161                 } catch (IllegalAccessException e) {
162                         throw new BugException(e);
163                 } catch (InvocationTargetException e) {
164                         throw Reflection.handleWrappedException(e);
165                 }
166         }
167         
168         /**
169          * Sets the value of the variable.
170          */
171         public void setValue(int v) {
172                 log.debug("Setting value " + v + " for " + this);
173                 try {
174                         setMethod.invoke(source, v);
175                 } catch (IllegalArgumentException e) {
176                         throw new BugException(e);
177                 } catch (IllegalAccessException e) {
178                         throw new BugException(e);
179                 } catch (InvocationTargetException e) {
180                         throw Reflection.handleWrappedException(e);
181                 }
182         }
183         
184         
185         /**
186          * Add a listener to the model.  Adds the model as a listener to the Component if this
187          * is the first listener.
188          * @param l Listener to add.
189          */
190         public void addChangeListener(EventListener l) {
191                 if (listeners.isEmpty()) {
192                         source.addChangeListener(this);
193                         lastValue = getValue();
194                 }
195                 
196                 listeners.add(l);
197                 log.verbose(this + " adding listener (total " + listeners.size() + "): " + l);
198         }
199         
200         /**
201          * Remove a listener from the model.  Removes the model from being a listener to the Component
202          * if this was the last listener of the model.
203          * @param l Listener to remove.
204          */
205         public void removeChangeListener(ChangeListener l) {
206                 listeners.remove(l);
207                 if (listeners.isEmpty()) {
208                         source.removeChangeListener(this);
209                 }
210                 log.verbose(this + " removing listener (total " + listeners.size() + "): " + l);
211         }
212         
213         
214         @Override
215         protected void finalize() throws Throwable {
216                 super.finalize();
217                 if (!listeners.isEmpty()) {
218                         log.warn(this + " being garbage-collected while having listeners " + listeners);
219                 }
220         };
221         
222         
223         public void fireStateChanged() {
224                 EventListener[] list = listeners.toArray(new EventListener[0] );
225                 EventObject event = new EventObject(this);
226                 ChangeEvent cevent = new ChangeEvent(this);
227                 firing++;
228                 for( EventListener l : list ) {
229                         if ( l instanceof ChangeListener) {
230                                 ((ChangeListener)l).stateChanged(cevent);
231                         } else if ( l instanceof StateChangeListener ) {
232                                 ((StateChangeListener)l).stateChanged(event);
233                         }
234                 }
235                 firing--;
236         }
237         
238         /**
239          * Called when the source changes.  Checks whether the modeled value has changed, and if
240          * it has, updates lastValue and generates ChangeEvents for all listeners of the model.
241          */
242         @Override
243         public void stateChanged(EventObject e) {
244                 int v = getValue();
245                 if (lastValue == v)
246                         return;
247                 lastValue = v;
248                 fireStateChanged();
249         }
250         
251         /**
252          * Explain the DoubleModel as a String.
253          */
254         @Override
255         public String toString() {
256                 if (toString == null) {
257                         toString = "IntegerModel[" + source.getClass().getSimpleName() + ":" + valueName + "]";
258                 }
259                 return toString;
260         }
261         
262 }