package net.sf.openrocket.gui.adaptors;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.EventObject;
import javax.swing.BoundedRangeModel;
import javax.swing.DefaultBoundedRangeModel;
import javax.swing.SpinnerModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sf.openrocket.logging.Markers;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.ChangeSource;
import net.sf.openrocket.util.Reflection;
import net.sf.openrocket.util.StateChangeListener;
public class IntegerModel implements StateChangeListener {
private static final Logger log = LoggerFactory.getLogger(IntegerModel.class);
//////////// JSpinner Model ////////////
private class IntegerSpinnerModel extends SpinnerNumberModel {
@Override
public Object getValue() {
return IntegerModel.this.getValue();
}
@Override
public void setValue(Object value) {
if (firing > 0) {
// Ignore, if called when model is sending events
log.trace("Ignoring call to SpinnerModel setValue for " + IntegerModel.this.toString() +
" value=" + value + ", currently firing events");
return;
}
Number num = (Number) value;
int newValue = num.intValue();
log.info(Markers.USER_MARKER, "SpinnerModel setValue called for " + IntegerModel.this.toString() + " newValue=" + newValue);
IntegerModel.this.setValue(newValue);
}
@Override
public Object getNextValue() {
int d = IntegerModel.this.getValue();
if (d >= maxValue)
return null;
return (d + 1);
}
@Override
public Object getPreviousValue() {
int d = IntegerModel.this.getValue();
if (d <= minValue)
return null;
return (d - 1);
}
@Override
public void addChangeListener(ChangeListener l) {
IntegerModel.this.addChangeListener(l);
}
@Override
public void removeChangeListener(ChangeListener l) {
IntegerModel.this.removeChangeListener(l);
}
}
/**
* Returns a new SpinnerModel with the same base as the DoubleModel.
* The values given to the JSpinner are in the currently selected units.
*
* @return A compatibility layer for a SpinnerModel.
*/
public SpinnerModel getSpinnerModel() {
return new IntegerSpinnerModel();
}
private class ValueSliderModel extends DefaultBoundedRangeModel implements BoundedRangeModel, StateChangeListener {
ValueSliderModel(){
super(IntegerModel.this.getValue(), 0, minValue, maxValue);
}
@Override
public void setValue(int newValue) {
IntegerModel.this.setValue(newValue);
}
@Override
public int getValue(){
return IntegerModel.this.getValue();
}
@Override
public void stateChanged(EventObject e) {
IntegerModel.this.fireStateChanged();
}
@Override
public void addChangeListener(ChangeListener l) {
IntegerModel.this.addChangeListener(l);
}
@Override
public void removeChangeListener(ChangeListener l) {
IntegerModel.this.removeChangeListener(l);
}
}
/**
* Returns a new BoundedRangeModel with the same base as the IntegerModel.
*
* @return A compatibility layer for Sliders.
*/
public BoundedRangeModel getSliderModel(){
return new ValueSliderModel();
}
//////////// Main model /////////////
/*
* The main model handles all values in SI units, i.e. no conversion is made within the model.
*/
private final ChangeSource source;
private final String valueName;
private final Method getMethod;
private final Method setMethod;
private final ArrayList<EventListener> listeners = new ArrayList<EventListener>();
private final int minValue;
private final int maxValue;
private String toString = null;
private int firing = 0; // >0 when model itself is sending events
// Used to differentiate changes in valueName and other changes in the source:
private int lastValue = 0;
/**
* Generates a new DoubleModel that changes the values of the specified source.
* The double value is read and written using the methods "get"/"set" + valueName.
*
* @param source Component whose parameter to use.
* @param valueName Name of methods used to get/set the parameter.
* @param min Minimum value allowed (in SI units)
* @param max Maximum value allowed (in SI units)
*/
public IntegerModel(ChangeSource source, String valueName, int min, int max) {
this.source = source;
this.valueName = valueName;
this.minValue = min;
this.maxValue = max;
try {
getMethod = source.getClass().getMethod("get" + valueName);
setMethod = source.getClass().getMethod("set" + valueName, int.class);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("get/set methods for value '" + valueName +
"' not present in class " + source.getClass().getCanonicalName());
}
}
public IntegerModel(ChangeSource source, String valueName, int min) {
this(source, valueName, min, Integer.MAX_VALUE);
}
public IntegerModel(ChangeSource source, String valueName) {
this(source, valueName, Integer.MIN_VALUE, Integer.MAX_VALUE);
}
/**
* Returns the value of the variable.
*/
public int getValue() {
try {
return (Integer) getMethod.invoke(source);
} catch (IllegalArgumentException e) {
throw new BugException(e);
} catch (IllegalAccessException e) {
throw new BugException(e);
} catch (InvocationTargetException e) {
throw Reflection.handleWrappedException(e);
}
}
/**
* Sets the value of the variable.
*/
public void setValue(int v) {
log.debug("Setting value " + v + " for " + this);
try {
setMethod.invoke(source, v);
} catch (IllegalArgumentException e) {
throw new BugException(e);
} catch (IllegalAccessException e) {
throw new BugException(e);
} catch (InvocationTargetException e) {
throw Reflection.handleWrappedException(e);
}
}
/**
* Add a listener to the model. Adds the model as a listener to the Component if this
* is the first listener.
* @param l Listener to add.
*/
public void addChangeListener(EventListener l) {
if (listeners.isEmpty()) {
source.addChangeListener(this);
lastValue = getValue();
}
listeners.add(l);
log.trace(this + " adding listener (total " + listeners.size() + "): " + l);
}
/**
* Remove a listener from the model. Removes the model from being a listener to the Component
* if this was the last listener of the model.
* @param l Listener to remove.
*/
public void removeChangeListener(ChangeListener l) {
listeners.remove(l);
if (listeners.isEmpty()) {
source.removeChangeListener(this);
}
log.trace(this + " removing listener (total " + listeners.size() + "): " + l);
}
@Override
protected void finalize() throws Throwable {
super.finalize();
if (!listeners.isEmpty()) {
log.warn(this + " being garbage-collected while having listeners " + listeners);
}
};
public void fireStateChanged() {
EventListener[] list = listeners.toArray(new EventListener[0] );
EventObject event = new EventObject(this);
ChangeEvent cevent = new ChangeEvent(this);
firing++;
for( EventListener l : list ) {
if ( l instanceof ChangeListener) {
((ChangeListener)l).stateChanged(cevent);
} else if ( l instanceof StateChangeListener ) {
((StateChangeListener)l).stateChanged(event);
}
}
firing--;
}
/**
* Called when the source changes. Checks whether the modeled value has changed, and if
* it has, updates lastValue and generates ChangeEvents for all listeners of the model.
*/
@Override
public void stateChanged(EventObject e) {
int v = getValue();
if (lastValue == v)
return;
lastValue = v;
fireStateChanged();
}
/**
* Explain the DoubleModel as a String.
*/
@Override
public String toString() {
if (toString == null) {
toString = "IntegerModel[" + source.getClass().getSimpleName() + ":" + valueName + "]";
}
return toString;
}
}