Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 484ff33c3a35c272f815545f647b5f38610c4b46 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
/*******************************************************************************
 * Copyright (c) 2007, 2008 Oracle. All rights reserved.
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0, which accompanies this distribution
 * and is available at http://www.eclipse.org/legal/epl-v10.html.
 * 
 * Contributors:
 *     Oracle - initial API and implementation
 ******************************************************************************/
package org.eclipse.jpt.utility.internal.model.value.swing;

import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeListener;
import org.eclipse.jpt.utility.internal.StringTools;
import org.eclipse.jpt.utility.internal.model.listener.awt.AWTPropertyChangeListenerWrapper;
import org.eclipse.jpt.utility.model.event.PropertyChangeEvent;
import org.eclipse.jpt.utility.model.listener.PropertyChangeListener;
import org.eclipse.jpt.utility.model.value.PropertyValueModel;
import org.eclipse.jpt.utility.model.value.WritablePropertyValueModel;

/**
 * This javax.swing.SpinnerNumberModel can be used to keep a ChangeListener
 * (e.g. a JSpinner) in synch with a PropertyValueModel that holds a number.
 * 
 * This class must be a sub-class of SpinnerNumberModel because of some
 * crappy jdk code....  ~bjv
 * @see javax.swing.JSpinner#createEditor(javax.swing.SpinnerModel)
 * 
 * If this class needs to be modified, it would behoove us to review the
 * other, similar classes:
 * @see DateSpinnerModelAdapter
 * @see ListSpinnerModelAdapter
 */
public class NumberSpinnerModelAdapter
	extends SpinnerNumberModel
{

	/**
	 * The default spinner value; used when the
	 * underlying model number value is null.
	 */
	private final Number defaultValue;

	/** A value model on the underlying number. */
	private final WritablePropertyValueModel<Number> numberHolder;

	/**
	 * A listener that allows us to synchronize with
	 * changes made to the underlying number.
	 */
	private final PropertyChangeListener numberChangeListener;


	// ********** constructors **********

	/**
	 * Constructor - the number holder is required.
	 * The default spinner value is zero.
	 * The step size is one.
	 */
	public NumberSpinnerModelAdapter(WritablePropertyValueModel<Number> numberHolder) {
		this(numberHolder, 0);
	}

	/**
	 * Constructor - the number holder is required.
	 * The step size is one.
	 */
	public NumberSpinnerModelAdapter(WritablePropertyValueModel<Number> numberHolder, int defaultValue) {
		this(numberHolder, null, null, new Integer(1), new Integer(defaultValue));
	}

	/**
	 * Constructor - the number holder is required.
	 * Use the minimum value as the default spinner value.
	 */
	public NumberSpinnerModelAdapter(WritablePropertyValueModel<Number> numberHolder, int minimum, int maximum, int stepSize) {
		this(numberHolder, minimum, maximum, stepSize, minimum);
	}

	/**
	 * Constructor - the number holder is required.
	 */
	public NumberSpinnerModelAdapter(WritablePropertyValueModel<Number> numberHolder, int minimum, int maximum, int stepSize, int defaultValue) {
		this(numberHolder, new Integer(minimum), new Integer(maximum), new Integer(stepSize), new Integer(defaultValue));
	}

	/**
	 * Constructor - the number holder is required.
	 * Use the minimum value as the default spinner value.
	 */
	public NumberSpinnerModelAdapter(WritablePropertyValueModel<Number> numberHolder, double value, double minimum, double maximum, double stepSize) {
		this(numberHolder, value, minimum, maximum, stepSize, minimum);
	}

	/**
	 * Constructor - the number holder is required.
	 */
	public NumberSpinnerModelAdapter(WritablePropertyValueModel<Number> numberHolder, double value, double minimum, double maximum, double stepSize, double defaultValue) {
		this(numberHolder, new Double(minimum), new Double(maximum), new Double(stepSize), new Double(defaultValue));
	}

	/**
	 * Constructor - the number holder is required.
	 */
	public NumberSpinnerModelAdapter(WritablePropertyValueModel<Number> numberHolder, Comparable<?> minimum, Comparable<?> maximum, Number stepSize, Number defaultValue) {
		super(numberHolder.getValue() == null ? defaultValue : (Number) numberHolder.getValue(), minimum, maximum, stepSize);
		this.numberHolder = numberHolder;
		this.numberChangeListener = this.buildNumberChangeListener();
		// postpone listening to the underlying number
		// until we have listeners ourselves...
		this.defaultValue = defaultValue;
	}


	// ********** initialization **********

	protected PropertyChangeListener buildNumberChangeListener() {
		return new AWTPropertyChangeListenerWrapper(this.buildNumberChangeListener_());
	}

	protected PropertyChangeListener buildNumberChangeListener_() {
		return new PropertyChangeListener() {
			public void propertyChanged(PropertyChangeEvent event) {
				NumberSpinnerModelAdapter.this.synchronize(event.getNewValue());
			}
			@Override
			public String toString() {
				return "number listener";
			}
		};
	}


	// ********** SpinnerModel implementation **********

	/**
	 * Extend to check whether this method is being called before we 
	 * have any listeners.
	 * This is necessary because some crappy jdk code gets the value
	 * from the model *before* listening to the model.  ~bjv
	 * @see javax.swing.JSpinner.DefaultEditor(javax.swing.JSpinner)
	 */
    @Override
	public Object getValue() {
		if (this.getChangeListeners().length == 0) {
			// sorry about this "lateral" call to super  ~bjv
			super.setValue(this.spinnerValueOf(this.numberHolder.getValue()));
		}
		return super.getValue();
	}

	/**
	 * Extend to update the underlying number directly.
	 * The resulting event will be ignored: @see #synchronizeDelegate(Object).
	 */
    @Override
	public void setValue(Object value) {
		super.setValue(value);
		this.numberHolder.setValue((Number) value);
	}

	/**
	 * Extend to start listening to the underlying number if necessary.
	 */
    @Override
	public void addChangeListener(ChangeListener listener) {
		if (this.getChangeListeners().length == 0) {
			this.numberHolder.addPropertyChangeListener(PropertyValueModel.VALUE, this.numberChangeListener);
			this.synchronize(this.numberHolder.getValue());
		}
		super.addChangeListener(listener);
	}

	/**
	 * Extend to stop listening to the underlying number if appropriate.
	 */
    @Override
	public void removeChangeListener(ChangeListener listener) {
		super.removeChangeListener(listener);
		if (this.getChangeListeners().length == 0) {
			this.numberHolder.removePropertyChangeListener(PropertyValueModel.VALUE, this.numberChangeListener);
		}
	}


	// ********** queries **********

	protected Number getDefaultValue() {
		return this.defaultValue;
	}

	/**
	 * Convert to a non-null value.
	 */
	protected Object spinnerValueOf(Object value) {
		return (value == null) ? this.getDefaultValue() : value;
	}


	// ********** behavior **********

	/**
	 * Set the spinner value if it has changed.
	 */
	void synchronize(Object value) {
		Object newValue = this.spinnerValueOf(value);
		// check to see whether the date has already been synchronized
		// (via #setValue())
		if ( ! this.getValue().equals(newValue)) {
			this.setValue(newValue);
		}
	}


	// ********** standard methods **********

	@Override
	public String toString() {
		return StringTools.buildToStringFor(this, this.numberHolder);
	}

}

Back to the top