package variableEditorComponents; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.Observable; import java.util.Observer; import javax.swing.JSlider; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import valueTypes.DecimalValue; import variableEditorUI.VariableEditorUIUpdateThread; import variables.Variable; import expressionConsole.ExpressionConsoleModel; /** * A JSlider which is bound to a Variable which contains a DecimalValue. When * the user moves the slider, the content of the Variable is updated. The scale * of the slider is also updated such that the value of the Variable is in the * middle of the slider range. When the Variable is changed from another source, * the scale of the slider is updated with the new contents of the Variable. * * @author Curran Kelleher * */ public class VariableBoundDecimalSlider extends JSlider implements ChangeListener, MouseListener, Observer, VariableEditorComponent { private static final long serialVersionUID = -7777499408692975081L; /** * The Variable to which this VariableBoundDecimalSlider is bound. */ Variable variable; /** * The resolution of the slider. */ final static int SLIDERSCALE = 800; /** * The value at the lowest slider position */ double minValue = 0; /** * The value at the highest slider position */ double maxValue = 10; /** * A flag indicating to ignore slider changes. (set to true when change is * self-induced) */ boolean ignoreSliderChange = false; /** * A DecimalValue which is used many times as an intermediate for assigning * a new value to the variable being edited. This is done to avoid creating * many new Objects. */ DecimalValue reusableValue = new DecimalValue(0); /** * The Value that is currently being displayed in this text field. This is * checked against the actual value of the Variable periodically to decide * whether or not to update the text with the current value of the Variable. * This String is parseable into the actual value. It is generated by the * method Value.toParseableString() */ String displayedValue = ""; /** * The value corresponding to the highest (rightmost) slider position when * the value of the Variable is zero. */ protected double maxValueForZeroValue = 1; /** * The value corresponding to the lowest (leftmost) slider position when the * value of the Variable is zero. */ protected double minValueForZeroValue = -1; /** * Construct a VariableBoundDecimalSlider which is bound to the specified * Variable. * * @param variable * the variable to edit */ public VariableBoundDecimalSlider(Variable variable) { this.variable = variable; // listen for the mouse release addMouseListener(this); // align the slider with our scale setMaximum(SLIDERSCALE); // get notifications every second. VariableEditorUIUpdateThread.getInstance().addObserver(this); // initialize the slider updateWithCurrentVariableValue(); // listen for slider movements addChangeListener(this); } /** * Updates the range of values that the slider covers such that the current * value of the variable makes it be in the center. * */ public void updateWithCurrentVariableValue() { double currentValue = DecimalValue.extractDoubleValue(variable .evaluate()); maxValue = currentValue * 2; minValue = 0; if (maxValue == 0) { maxValue = maxValueForZeroValue; minValue = minValueForZeroValue; } else if (maxValue < 0) { minValue = maxValue; maxValue = 0; } // ignore the self-induced change ignoreSliderChange = true; // set the slider position setValue((int) ((currentValue - minValue) / (maxValue - minValue) * SLIDERSCALE)); // don't ignore subsequent changes ignoreSliderChange = false; // update the displayed Value displayedValue = "" + currentValue; } /** * Listens to slider changes, updates the variable's value * * @param a * the event from the slider change */ public void stateChanged(ChangeEvent a) { if (!ignoreSliderChange) { reusableValue.value = getCurrentSliderValue(); variable.set(reusableValue); displayedValue = reusableValue.toParseableString(); } } /** * * @return the current value (which the Variable will be set to) derived * from the slider position. */ protected double getCurrentSliderValue() { return minValue + ((double) getValue() / SLIDERSCALE) * (maxValue - minValue); } public void mouseClicked(MouseEvent e) { } public void mousePressed(MouseEvent e) { } /** * Log the change when the mouse is released. */ public void mouseReleased(MouseEvent e) { // log the change for correct replay ExpressionConsoleModel.getInstance().enterExpression( variable.toString() + " = " + variable.evaluate().toParseableString()); updateWithCurrentVariableValue(); } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } /** * Get the update from the VariableEditorUIUpdateThread */ public void update(Observable o, Object arg) { if (o == VariableEditorUIUpdateThread.getInstance()) // if this component is no longer usable, remove it as an Observer. if (!isDisplayable()) VariableEditorUIUpdateThread.getInstance().deleteObserver(this); else if (!variable.evaluate().toParseableString().equals( displayedValue)) if (!getValueIsAdjusting()) updateWithCurrentVariableValue(); } /** * Sets up a VariableEditorComponent such that * updateWithCurrentVariableValue() will be called in it whenever the * slider's position changes. * * @param componentToUpdate * the VariableEditorComponent to update when the slider's * position changes. */ public void bindToVariableEditorComponent( VariableEditorComponent componentToUpdate) { final VariableEditorComponent componentToReallyUpdate = componentToUpdate; addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { componentToReallyUpdate.updateWithCurrentVariableValue(); } }); } }