Added a label to the preset selector.
[debian/openrocket] / core / src / net / sf / openrocket / gui / adaptors / DoubleModel.java
1 package net.sf.openrocket.gui.adaptors;
2
3 import java.awt.event.ActionEvent;
4 import java.beans.PropertyChangeEvent;
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.EventListener;
10 import java.util.EventObject;
11
12 import javax.swing.AbstractAction;
13 import javax.swing.Action;
14 import javax.swing.BoundedRangeModel;
15 import javax.swing.SpinnerModel;
16 import javax.swing.SpinnerNumberModel;
17 import javax.swing.event.ChangeEvent;
18 import javax.swing.event.ChangeListener;
19
20 import net.sf.openrocket.logging.LogHelper;
21 import net.sf.openrocket.startup.Application;
22 import net.sf.openrocket.unit.Unit;
23 import net.sf.openrocket.unit.UnitGroup;
24 import net.sf.openrocket.util.BugException;
25 import net.sf.openrocket.util.ChangeSource;
26 import net.sf.openrocket.util.Invalidatable;
27 import net.sf.openrocket.util.Invalidator;
28 import net.sf.openrocket.util.MathUtil;
29 import net.sf.openrocket.util.MemoryManagement;
30 import net.sf.openrocket.util.Reflection;
31 import net.sf.openrocket.util.StateChangeListener;
32
33
34 /**
35  * A model connector that can read and modify any value of any ChangeSource that
36  * has the appropriate get/set methods defined.  
37  * 
38  * The variable is defined in the constructor by providing the variable name as a string
39  * (e.g. "Radius" -> getRadius()/setRadius()).  Additional scaling may be applied, e.g. a 
40  * DoubleModel for the diameter can be defined by the variable "Radius" and a multiplier of 2.
41  * 
42  * Sub-models suitable for JSpinners and other components are available from the appropriate
43  * methods.
44  * 
45  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
46  */
47
48 public class DoubleModel implements StateChangeListener, ChangeSource, Invalidatable {
49         private static final LogHelper log = Application.getLogger();
50         
51
52         public static final DoubleModel ZERO = new DoubleModel(0);
53         
54         //////////// JSpinner Model ////////////
55         
56         /**
57          * Model suitable for JSpinner using JSpinner.NumberEditor.  It extends SpinnerNumberModel
58          * to be compatible with the NumberEditor, but only has the necessary methods defined.
59          */
60         private class ValueSpinnerModel extends SpinnerNumberModel implements Invalidatable {
61                 
62                 @Override
63                 public Object getValue() {
64                         return currentUnit.toUnit(DoubleModel.this.getValue());
65                 }
66                 
67                 @Override
68                 public void setValue(Object value) {
69                         if (firing > 0) {
70                                 // Ignore, if called when model is sending events
71                                 log.verbose("Ignoring call to SpinnerModel setValue for " + DoubleModel.this.toString() +
72                                                 " value=" + value + ", currently firing events");
73                                 return;
74                         }
75                         Number num = (Number) value;
76                         double newValue = num.doubleValue();
77                         double converted = currentUnit.fromUnit(newValue);
78                         
79                         log.user("SpinnerModel setValue called for " + DoubleModel.this.toString() + " newValue=" + newValue +
80                                         " converted=" + converted);
81                         DoubleModel.this.setValue(converted);
82                         
83                 }
84                 
85                 @Override
86                 public Object getNextValue() {
87                         double d = currentUnit.toUnit(DoubleModel.this.getValue());
88                         double max = currentUnit.toUnit(maxValue);
89                         if (MathUtil.equals(d, max))
90                                 return null;
91                         d = currentUnit.getNextValue(d);
92                         if (d > max)
93                                 d = max;
94                         return d;
95                 }
96                 
97                 @Override
98                 public Object getPreviousValue() {
99                         double d = currentUnit.toUnit(DoubleModel.this.getValue());
100                         double min = currentUnit.toUnit(minValue);
101                         if (MathUtil.equals(d, min))
102                                 return null;
103                         d = currentUnit.getPreviousValue(d);
104                         if (d < min)
105                                 d = min;
106                         return d;
107                 }
108                 
109                 
110                 @Override
111                 public Comparable<Double> getMinimum() {
112                         return currentUnit.toUnit(minValue);
113                 }
114                 
115                 @Override
116                 public Comparable<Double> getMaximum() {
117                         return currentUnit.toUnit(maxValue);
118                 }
119                 
120                 
121                 @Override
122                 public void addChangeListener(ChangeListener l) {
123                         DoubleModel.this.addChangeListener(l);
124                 }
125                 
126                 @Override
127                 public void removeChangeListener(ChangeListener l) {
128                         DoubleModel.this.removeChangeListener(l);
129                 }
130                 
131                 @Override
132                 public void invalidate() {
133                         DoubleModel.this.invalidate();
134                 }
135         }
136         
137         /**
138          * Returns a new SpinnerModel with the same base as the DoubleModel.
139          * The values given to the JSpinner are in the currently selected units.
140          * 
141          * @return  A compatibility layer for a SpinnerModel.
142          */
143         public SpinnerModel getSpinnerModel() {
144                 return new ValueSpinnerModel();
145         }
146         
147         
148
149
150
151         ////////////  JSlider model  ////////////
152         
153         private class ValueSliderModel implements BoundedRangeModel, StateChangeListener, Invalidatable {
154                 private static final int MAX = 1000;
155                 
156                 /*
157                  * Use linear scale  value = linear1 * x + linear0  when x < linearPosition
158                  * Use quadratic scale  value = quad2 * x^2 + quad1 * x + quad0  otherwise
159                  */
160                 private final boolean islinear;
161                 
162                 // Linear in range x <= linearPosition
163                 private final double linearPosition;
164                 
165                 // May be changing DoubleModels when using linear model
166                 private final DoubleModel min, mid, max;
167                 
168                 // Linear multiplier and constant
169                 //private final double linear1;
170                 //private final double linear0;
171                 
172                 // Non-linear multiplier, exponent and constant
173                 private double quad2, quad1, quad0;
174
175                 public ValueSliderModel(DoubleModel min, DoubleModel max) {
176                         this.islinear = true;
177                         linearPosition = 1.0;
178                         
179                         this.min = min;
180                         this.mid = max; // Never use exponential scale
181                         this.max = max;
182                         
183                         min.addChangeListener(this);
184                         max.addChangeListener(this);
185                         
186                         quad2 = quad1 = quad0 = 0; // Not used
187                 }
188                 
189                 
190
191                 /**
192                  * Generate a linear model from min to max.
193                  */
194                 public ValueSliderModel(double min, double max) {
195                         this.islinear = true;
196                         linearPosition = 1.0;
197                         
198                         this.min = new DoubleModel(min);
199                         this.mid = new DoubleModel(max); // Never use exponential scale
200                         this.max = new DoubleModel(max);
201                         
202                         quad2 = quad1 = quad0 = 0; // Not used
203                 }
204                 
205                 public ValueSliderModel(double min, double mid, double max) {
206                         this(min, 0.5, mid, max);
207                 }
208                 
209                 public ValueSliderModel(double min, double mid, DoubleModel max) {
210                         this(min, 0.5, mid, max);
211                 }
212
213                 /*
214                  * v(x)  = mul * x^exp + add
215                  * 
216                  * v(pos)  = mul * pos^exp + add = mid
217                  * v(1)    = mul + add = max
218                  * v'(pos) = mul*exp * pos^(exp-1) = linearMul
219                  */
220                 public ValueSliderModel(double min, double pos, double mid, double max ) {
221                         this(min, pos, mid, new DoubleModel(max));
222                 }
223                 public ValueSliderModel(double min, double pos, double mid, DoubleModel max) {
224                         this.min = new DoubleModel(min);
225                         this.mid = new DoubleModel(mid);
226                         this.max = max;
227
228                         this.islinear = false;
229                         
230                         max.addChangeListener(this);
231                         
232                         linearPosition = pos;
233                         //linear0 = min;
234                         //linear1 = (mid-min)/pos;
235                         
236                         if (!(min < mid && mid <= max.getValue() && 0 < pos && pos < 1)) {
237                                 throw new IllegalArgumentException("Bad arguments for ValueSliderModel " +
238                                                 "min=" + min + " mid=" + mid + " max=" + max + " pos=" + pos);
239                         }
240                         
241                         updateExponentialParameters();
242                         
243                 }
244                 
245                 private void updateExponentialParameters() {
246                         double pos = this.linearPosition;
247                         double minValue = this.min.getValue();
248                         double midValue = this.mid.getValue();
249                         double maxValue = this.max.getValue();
250                         /*
251                          * quad2..0 are calculated such that
252                          *   f(pos)  = mid      - continuity
253                          *   f(1)    = max      - end point
254                          *   f'(pos) = linear1  - continuity of derivative
255                          */
256                         double delta = (midValue - minValue) / pos;
257                         quad2 = (maxValue - midValue - delta + delta * pos) / pow2(pos - 1);
258                         quad1 = (delta + 2 * (midValue - maxValue) * pos - delta * pos * pos) / pow2(pos - 1);
259                         quad0 = (midValue - (2 * midValue + delta) * pos + (maxValue + delta) * pos * pos) / pow2(pos - 1);
260                 }
261                 
262                 private double pow2(double x) {
263                         return x * x;
264                 }
265                 
266                 @Override
267                 public int getValue() {
268                         double value = DoubleModel.this.getValue();
269                         if (value <= min.getValue())
270                                 return 0;
271                         if (value >= max.getValue())
272                                 return MAX;
273                         
274                         double x;
275                         if (value <= mid.getValue()) {
276                                 // Use linear scale
277                                 //linear0 = min;
278                                 //linear1 = (mid-min)/pos;
279                                 
280                                 x = (value - min.getValue()) * linearPosition / (mid.getValue() - min.getValue());
281                         } else {
282                                 // Use quadratic scale
283                                 // Further solution of the quadratic equation
284                                 //   a*x^2 + b*x + c-value == 0
285                                 x = (MathUtil.safeSqrt(quad1 * quad1 - 4 * quad2 * (quad0 - value)) - quad1) / (2 * quad2);
286                         }
287                         return (int) (x * MAX);
288                 }
289                 
290                 
291                 @Override
292                 public void setValue(int newValue) {
293                         if (firing > 0) {
294                                 // Ignore loops
295                                 log.verbose("Ignoring call to SliderModel setValue for " + DoubleModel.this.toString() +
296                                                 " value=" + newValue + ", currently firing events");
297                                 return;
298                         }
299                         
300                         double x = (double) newValue / MAX;
301                         double scaledValue;
302                         
303                         if (x <= linearPosition) {
304                                 // Use linear scale
305                                 //linear0 = min;
306                                 //linear1 = (mid-min)/pos;
307                                 
308                                 scaledValue = (mid.getValue() - min.getValue()) / linearPosition * x + min.getValue();
309                         } else {
310                                 // Use quadratic scale
311                                 scaledValue = quad2 * x * x + quad1 * x + quad0;
312                         }
313                         
314                         double converted = currentUnit.fromUnit(currentUnit.round(currentUnit.toUnit(scaledValue)));
315                         log.user("SliderModel setValue called for " + DoubleModel.this.toString() + " newValue=" + newValue +
316                                         " scaledValue=" + scaledValue + " converted=" + converted);
317                         DoubleModel.this.setValue(converted);
318                 }
319                 
320                 
321                 // Static get-methods
322                 private boolean isAdjusting;
323                 
324                 @Override
325                 public int getExtent() {
326                         return 0;
327                 }
328                 
329                 @Override
330                 public int getMaximum() {
331                         return MAX;
332                 }
333                 
334                 @Override
335                 public int getMinimum() {
336                         return 0;
337                 }
338                 
339                 @Override
340                 public boolean getValueIsAdjusting() {
341                         return isAdjusting;
342                 }
343                 
344                 // Ignore set-values
345                 @Override
346                 public void setExtent(int newExtent) {
347                 }
348                 
349                 @Override
350                 public void setMaximum(int newMaximum) {
351                 }
352                 
353                 @Override
354                 public void setMinimum(int newMinimum) {
355                 }
356                 
357                 @Override
358                 public void setValueIsAdjusting(boolean b) {
359                         isAdjusting = b;
360                 }
361                 
362                 @Override
363                 public void setRangeProperties(int value, int extent, int min, int max, boolean adjusting) {
364                         setValueIsAdjusting(adjusting);
365                         setValue(value);
366                 }
367                 
368                 // Pass change listeners to the underlying model
369                 @Override
370                 public void addChangeListener(ChangeListener l) {
371                         DoubleModel.this.addChangeListener(l);
372                 }
373                 
374                 @Override
375                 public void removeChangeListener(ChangeListener l) {
376                         DoubleModel.this.removeChangeListener(l);
377                 }
378                 
379                 @Override
380                 public void invalidate() {
381                         DoubleModel.this.invalidate();
382                 }
383                 
384                 @Override
385                 public void stateChanged(EventObject e) {
386                         // Min or max range has changed.
387                         if ( !islinear ) {
388                                 double midValue = (max.getValue() - min.getValue()) /3.0;
389                                 mid.setValue(midValue);
390                                 updateExponentialParameters();
391                         }
392                         // Fire if not already firing
393                         if (firing == 0)
394                                 fireStateChanged();
395                 }
396         }
397         
398         
399         public BoundedRangeModel getSliderModel(DoubleModel min, DoubleModel max) {
400                 return new ValueSliderModel(min, max);
401         }
402         
403         public BoundedRangeModel getSliderModel(double min, double max) {
404                 return new ValueSliderModel(min, max);
405         }
406         
407         public BoundedRangeModel getSliderModel(double min, double mid, double max) {
408                 return new ValueSliderModel(min, mid, max);
409         }
410         
411         public BoundedRangeModel getSliderModel(double min, double mid, DoubleModel max) {
412                 return new ValueSliderModel(min, mid, max);
413         }
414         
415         public BoundedRangeModel getSliderModel(double min, double pos, double mid, double max) {
416                 return new ValueSliderModel(min, pos, mid, max);
417         }
418         
419         
420
421
422
423         ////////////  Action model  ////////////
424         
425         private class AutomaticActionModel extends AbstractAction implements StateChangeListener, Invalidatable {
426                 private boolean oldValue = false;
427                 
428                 public AutomaticActionModel() {
429                         oldValue = isAutomatic();
430                         addChangeListener(this);
431                 }
432                 
433                 
434                 @Override
435                 public boolean isEnabled() {
436                         return isAutomaticAvailable();
437                 }
438                 
439                 @Override
440                 public Object getValue(String key) {
441                         if (key.equals(Action.SELECTED_KEY)) {
442                                 oldValue = isAutomatic();
443                                 return oldValue;
444                         }
445                         return super.getValue(key);
446                 }
447                 
448                 @Override
449                 public void putValue(String key, Object value) {
450                         if (firing > 0) {
451                                 log.verbose("Ignoring call to ActionModel putValue for " + DoubleModel.this.toString() +
452                                                 " key=" + key + " value=" + value + ", currently firing events");
453                                 return;
454                         }
455                         if (key.equals(Action.SELECTED_KEY) && (value instanceof Boolean)) {
456                                 log.user("ActionModel putValue called for " + DoubleModel.this.toString() +
457                                                 " key=" + key + " value=" + value);
458                                 oldValue = (Boolean) value;
459                                 setAutomatic((Boolean) value);
460                         } else {
461                                 log.debug("Passing ActionModel putValue call to supermethod for " + DoubleModel.this.toString() +
462                                                 " key=" + key + " value=" + value);
463                                 super.putValue(key, value);
464                         }
465                 }
466                 
467                 // Implement a wrapper to the ChangeListeners
468                 ArrayList<PropertyChangeListener> propertyChangeListeners =
469                                 new ArrayList<PropertyChangeListener>();
470                 
471                 @Override
472                 public void addPropertyChangeListener(PropertyChangeListener listener) {
473                         propertyChangeListeners.add(listener);
474                         DoubleModel.this.addChangeListener(this);
475                 }
476                 
477                 @Override
478                 public void removePropertyChangeListener(PropertyChangeListener listener) {
479                         propertyChangeListeners.remove(listener);
480                         if (propertyChangeListeners.isEmpty())
481                                 DoubleModel.this.removeChangeListener(this);
482                 }
483                 
484                 // If the value has changed, generate an event to the listeners
485                 @Override
486                 public void stateChanged(EventObject e) {
487                         boolean newValue = isAutomatic();
488                         if (oldValue == newValue)
489                                 return;
490                         PropertyChangeEvent event = new PropertyChangeEvent(this, Action.SELECTED_KEY,
491                                         oldValue, newValue);
492                         oldValue = newValue;
493                         Object[] l = propertyChangeListeners.toArray();
494                         for (int i = 0; i < l.length; i++) {
495                                 ((PropertyChangeListener) l[i]).propertyChange(event);
496                         }
497                 }
498                 
499                 @Override
500                 public void actionPerformed(ActionEvent e) {
501                         // Setting performed in putValue
502                 }
503                 
504                 @Override
505                 public void invalidate() {
506                         DoubleModel.this.invalidate();
507                 }
508         }
509         
510         /**
511          * Returns a new Action corresponding to the changes of the automatic setting
512          * property of the value model.  This may be used directly with e.g. check buttons.
513          * 
514          * @return  A compatibility layer for an Action.
515          */
516         public Action getAutomaticAction() {
517                 return new AutomaticActionModel();
518         }
519         
520         
521
522
523
524         ////////////  Main model  /////////////
525         
526         /*
527          * The main model handles all values in SI units, i.e. no conversion is made within the model.
528          */
529
530         private final ChangeSource source;
531         private final String valueName;
532         private final double multiplier;
533         
534         private final Method getMethod;
535         private final Method setMethod;
536         
537         private final Method getAutoMethod;
538         private final Method setAutoMethod;
539         
540         private final ArrayList<EventListener> listeners = new ArrayList<EventListener>();
541         
542         private final UnitGroup units;
543         private Unit currentUnit;
544         
545         private final double minValue;
546         private double maxValue;
547         
548         private String toString = null;
549         
550
551         private int firing = 0; //  >0 when model itself is sending events
552         
553
554         // Used to differentiate changes in valueName and other changes in the component:
555         private double lastValue = 0;
556         private boolean lastAutomatic = false;
557         
558         private Invalidator invalidator = new Invalidator(this);
559         
560         
561         /**
562          * Generate a DoubleModel that contains an internal double value.
563          * 
564          * @param value         the initial value.
565          */
566         public DoubleModel(double value) {
567                 this(value, UnitGroup.UNITS_NONE, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
568         }
569         
570         /**
571          * Generate a DoubleModel that contains an internal double value.
572          * 
573          * @param value         the initial value.
574          * @param unit          the unit for the value.
575          */
576         public DoubleModel(double value, UnitGroup unit) {
577                 this(value, unit, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
578         }
579         
580         /**
581          * Generate a DoubleModel that contains an internal double value.
582          * 
583          * @param value         the initial value.
584          * @param unit          the unit for the value.
585          * @param min           minimum value.
586          */
587         public DoubleModel(double value, UnitGroup unit, double min) {
588                 this(value, unit, min, Double.POSITIVE_INFINITY);
589         }
590         
591         /**
592          * Generate a DoubleModel that contains an internal double value.
593          * 
594          * @param value         the initial value.
595          * @param unit          the unit for the value.
596          * @param min           minimum value.
597          * @param max           maximum value.
598          */
599         public DoubleModel(double value, UnitGroup unit, double min, double max) {
600                 this.lastValue = value;
601                 this.minValue = min;
602                 this.maxValue = max;
603                 
604                 source = null;
605                 valueName = "Constant value";
606                 multiplier = 1;
607                 
608                 getMethod = setMethod = null;
609                 getAutoMethod = setAutoMethod = null;
610                 units = unit;
611                 currentUnit = units.getDefaultUnit();
612         }
613         
614         
615         /**
616          * Generates a new DoubleModel that changes the values of the specified component.
617          * The double value is read and written using the methods "get"/"set" + valueName.
618          *  
619          * @param source Component whose parameter to use.
620          * @param valueName Name of methods used to get/set the parameter.
621          * @param multiplier Value shown by the model is the value from component.getXXX * multiplier
622          * @param min Minimum value allowed (in SI units)
623          * @param max Maximum value allowed (in SI units)
624          */
625         public DoubleModel(ChangeSource source, String valueName, double multiplier, UnitGroup unit,
626                         double min, double max) {
627                 this.source = source;
628                 this.valueName = valueName;
629                 this.multiplier = multiplier;
630                 
631                 this.units = unit;
632                 currentUnit = units.getDefaultUnit();
633                 
634                 this.minValue = min;
635                 this.maxValue = max;
636                 
637                 try {
638                         getMethod = source.getClass().getMethod("get" + valueName);
639                 } catch (NoSuchMethodException e) {
640                         throw new IllegalArgumentException("get method for value '" + valueName +
641                                         "' not present in class " + source.getClass().getCanonicalName());
642                 }
643                 
644                 Method s = null;
645                 try {
646                         s = source.getClass().getMethod("set" + valueName, double.class);
647                 } catch (NoSuchMethodException e1) {
648                 } // Ignore
649                 setMethod = s;
650                 
651                 // Automatic selection methods
652                 
653                 Method set = null, get = null;
654                 
655                 try {
656                         get = source.getClass().getMethod("is" + valueName + "Automatic");
657                         set = source.getClass().getMethod("set" + valueName + "Automatic", boolean.class);
658                 } catch (NoSuchMethodException e) {
659                 } // ignore
660                 
661                 if (set != null && get != null) {
662                         getAutoMethod = get;
663                         setAutoMethod = set;
664                 } else {
665                         getAutoMethod = null;
666                         setAutoMethod = null;
667                 }
668                 
669         }
670         
671         public DoubleModel(ChangeSource source, String valueName, double multiplier, UnitGroup unit,
672                         double min) {
673                 this(source, valueName, multiplier, unit, min, Double.POSITIVE_INFINITY);
674         }
675         
676         public DoubleModel(ChangeSource source, String valueName, double multiplier, UnitGroup unit) {
677                 this(source, valueName, multiplier, unit, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
678         }
679         
680         public DoubleModel(ChangeSource source, String valueName, UnitGroup unit,
681                         double min, double max) {
682                 this(source, valueName, 1.0, unit, min, max);
683         }
684         
685         public DoubleModel(ChangeSource source, String valueName, UnitGroup unit, double min) {
686                 this(source, valueName, 1.0, unit, min, Double.POSITIVE_INFINITY);
687         }
688         
689         public DoubleModel(ChangeSource source, String valueName, UnitGroup unit) {
690                 this(source, valueName, 1.0, unit, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
691         }
692         
693         public DoubleModel(ChangeSource source, String valueName) {
694                 this(source, valueName, 1.0, UnitGroup.UNITS_NONE,
695                                 Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
696         }
697         
698         public DoubleModel(ChangeSource source, String valueName, double min) {
699                 this(source, valueName, 1.0, UnitGroup.UNITS_NONE, min, Double.POSITIVE_INFINITY);
700         }
701         
702         public DoubleModel(ChangeSource source, String valueName, double min, double max) {
703                 this(source, valueName, 1.0, UnitGroup.UNITS_NONE, min, max);
704         }
705         
706         
707
708         /**
709          * Returns the value of the variable (in SI units).
710          */
711         public double getValue() {
712                 if (getMethod == null) // Constant value
713                         return lastValue;
714                 
715                 try {
716                         return (Double) getMethod.invoke(source) * multiplier;
717                 } catch (IllegalArgumentException e) {
718                         throw new BugException("Unable to invoke getMethod of " + this, e);
719                 } catch (IllegalAccessException e) {
720                         throw new BugException("Unable to invoke getMethod of " + this, e);
721                 } catch (InvocationTargetException e) {
722                         throw Reflection.handleWrappedException(e);
723                 }
724         }
725         
726         /**
727          * Sets the value of the variable.
728          * @param v New value for parameter in SI units.
729          */
730         public void setValue(double v) {
731                 checkState(true);
732                 
733                 log.debug("Setting value " + v + " for " + this);
734                 if (setMethod == null) {
735                         if (getMethod != null) {
736                                 throw new BugException("setMethod not available for variable '" + valueName +
737                                                 "' in class " + source.getClass().getCanonicalName());
738                         }
739                         lastValue = v;
740                         fireStateChanged();
741                         return;
742                 }
743                 
744                 try {
745                         setMethod.invoke(source, v / multiplier);
746                 } catch (IllegalArgumentException e) {
747                         throw new BugException("Unable to invoke setMethod of " + this, e);
748                 } catch (IllegalAccessException e) {
749                         throw new BugException("Unable to invoke setMethod of " + this, e);
750                 } catch (InvocationTargetException e) {
751                         throw Reflection.handleWrappedException(e);
752                 }
753         }
754         
755         /**
756          * Returns whether setting the value automatically is available.
757          */
758         public boolean isAutomaticAvailable() {
759                 return (getAutoMethod != null) && (setAutoMethod != null);
760         }
761         
762         /**
763          * Returns whether the value is currently being set automatically.
764          * Returns false if automatic setting is not available at all.
765          */
766         public boolean isAutomatic() {
767                 if (getAutoMethod == null)
768                         return false;
769                 
770                 try {
771                         return (Boolean) getAutoMethod.invoke(source);
772                 } catch (IllegalArgumentException e) {
773                         throw new BugException("Method call failed", e);
774                 } catch (IllegalAccessException e) {
775                         throw new BugException("Method call failed", e);
776                 } catch (InvocationTargetException e) {
777                         throw Reflection.handleWrappedException(e);
778                 }
779         }
780         
781         /**
782          * Sets whether the value should be set automatically.  Simply fires a
783          * state change event if automatic setting is not available.
784          */
785         public void setAutomatic(boolean auto) {
786                 checkState(true);
787                 
788                 if (setAutoMethod == null) {
789                         log.debug("Setting automatic to " + auto + " for " + this + ", automatic not available");
790                         fireStateChanged(); // in case something is out-of-sync
791                         return;
792                 }
793                 
794                 log.debug("Setting automatic to " + auto + " for " + this);
795                 lastAutomatic = auto;
796                 try {
797                         setAutoMethod.invoke(source, auto);
798                 } catch (IllegalArgumentException e) {
799                         throw new BugException(e);
800                 } catch (IllegalAccessException e) {
801                         throw new BugException(e);
802                 } catch (InvocationTargetException e) {
803                         throw Reflection.handleWrappedException(e);
804                 }
805         }
806         
807         
808         /**
809          * Returns the current Unit.  At the beginning it is the default unit of the UnitGroup.
810          * @return The most recently set unit.
811          */
812         public Unit getCurrentUnit() {
813                 return currentUnit;
814         }
815         
816         /**
817          * Sets the current Unit.  The unit must be one of those included in the UnitGroup.
818          * @param u  The unit to set active.
819          */
820         public void setCurrentUnit(Unit u) {
821                 checkState(true);
822                 if (currentUnit == u)
823                         return;
824                 log.debug("Setting unit for " + this + " to '" + u + "'");
825                 currentUnit = u;
826                 fireStateChanged();
827         }
828         
829         
830         /**
831          * Returns the UnitGroup associated with the parameter value.
832          *
833          * @return The UnitGroup given to the constructor.
834          */
835         public UnitGroup getUnitGroup() {
836                 return units;
837         }
838         
839         
840
841         /**
842          * Add a listener to the model.  Adds the model as a listener to the value source if this
843          * is the first listener.
844          * @param l Listener to add.
845          */
846         @Override
847         public void addChangeListener(EventListener l) {
848                 checkState(true);
849                 
850                 if (listeners.isEmpty()) {
851                         if (source != null) {
852                                 source.addChangeListener(this);
853                                 lastValue = getValue();
854                                 lastAutomatic = isAutomatic();
855                         }
856                 }
857                 
858                 listeners.add(l);
859                 log.verbose(this + " adding listener (total " + listeners.size() + "): " + l);
860         }
861         
862         /**
863          * Remove a listener from the model.  Removes the model from being a listener to the Component
864          * if this was the last listener of the model.
865          * @param l Listener to remove.
866          */
867         @Override
868         public void removeChangeListener(EventListener l) {
869                 checkState(false);
870                 
871                 listeners.remove(l);
872                 if (listeners.isEmpty() && source != null) {
873                         source.removeChangeListener(this);
874                 }
875                 log.verbose(this + " removing listener (total " + listeners.size() + "): " + l);
876         }
877         
878         
879         /**
880          * Invalidates this model by removing all listeners and removing this from
881          * listening to the source.  After invalidation no listeners can be added to this
882          * model and the value cannot be set.
883          */
884         @Override
885         public void invalidate() {
886                 log.verbose("Invalidating " + this);
887                 invalidator.invalidate();
888                 
889                 if (!listeners.isEmpty()) {
890                         log.warn("Invalidating " + this + " while still having listeners " + listeners);
891                 }
892                 listeners.clear();
893                 if (source != null) {
894                         source.removeChangeListener(this);
895                 }
896                 MemoryManagement.collectable(this);
897         }
898         
899         
900         private void checkState(boolean error) {
901                 invalidator.check(error);
902         }
903         
904         
905         @Override
906         protected void finalize() throws Throwable {
907                 super.finalize();
908                 if (!listeners.isEmpty()) {
909                         log.warn(this + " being garbage-collected while having listeners " + listeners);
910                 }
911         };
912         
913         
914         /**
915          * Fire a ChangeEvent to all listeners.
916          */
917         protected void fireStateChanged() {
918                 checkState(true);
919                 
920                 EventObject event = new EventObject(this);
921                 ChangeEvent cevent = new ChangeEvent(this);
922                 firing++;
923                 // Copy the list before iterating to prevent concurrent modification exceptions.
924                 EventListener[] ls = listeners.toArray(new EventListener[0]);
925                 for (EventListener l : ls) {
926                         if ( l instanceof StateChangeListener ) {
927                                 ((StateChangeListener)l).stateChanged(event);
928                         } else if ( l instanceof ChangeListener ) {
929                                 ((ChangeListener)l).stateChanged(cevent);
930                         }
931                 }
932                 firing--;
933         }
934         
935         /**
936          * Called when the component changes.  Checks whether the modeled value has changed, and if
937          * it has, updates lastValue and generates ChangeEvents for all listeners of the model.
938          */
939         @Override
940         public void stateChanged(EventObject e) {
941                 checkState(true);
942                 
943                 double v = getValue();
944                 boolean b = isAutomatic();
945                 if (lastValue == v && lastAutomatic == b)
946                         return;
947                 lastValue = v;
948                 lastAutomatic = b;
949                 fireStateChanged();
950         }
951         
952         
953         /**
954          * Explain the DoubleModel as a String.
955          */
956         @Override
957         public String toString() {
958                 if (toString == null) {
959                         if (source == null) {
960                                 toString = "DoubleModel[constant=" + lastValue + "]";
961                         } else {
962                                 toString = "DoubleModel[" + source.getClass().getSimpleName() + ":" + valueName + "]";
963                         }
964                 }
965                 return toString;
966         }
967 }