View Javadoc

1   package org.jcurl.demo.tactics;
2   
3   import java.awt.event.FocusEvent;
4   import java.awt.event.FocusListener;
5   import java.awt.event.ItemEvent;
6   import java.awt.event.ItemListener;
7   
8   import javax.swing.BoxLayout;
9   import javax.swing.DefaultComboBoxModel;
10  import javax.swing.JComboBox;
11  import javax.swing.JComponent;
12  import javax.swing.JLabel;
13  import javax.swing.JSpinner;
14  import javax.swing.SpinnerNumberModel;
15  import javax.swing.JSpinner.DefaultEditor;
16  import javax.swing.event.ChangeEvent;
17  import javax.swing.event.ChangeListener;
18  
19  import org.apache.commons.logging.Log;
20  import org.jcurl.core.api.ChangeSupport;
21  import org.jcurl.core.api.IChangeSupport;
22  import org.jcurl.core.api.Unit;
23  import org.jcurl.core.log.JCLoggerFactory;
24  
25  /**
26   * Combine a numerical {@link JSpinner} and a {@link Unit} {@link JComboBox}.
27   * 
28   * @author <a href="mailto:JCurl@mro.name">M. Rohrmoser </a>
29   * @version $Id$
30   */
31  public class JSpinnerNumberUnit extends JComponent implements ItemListener,
32  		FocusListener, ChangeListener, IChangeSupport {
33  
34  	private static final Log log = JCLoggerFactory
35  			.getLogger(JSpinnerNumberUnit.class);
36  
37  	private static final long serialVersionUID = 7309659176999252671L;
38  
39  	private static double convert(final double v, final Unit src, final Unit dst) {
40  		if (src == dst || src == null || src == Unit.NONE || dst == null
41  				|| dst == Unit.NONE)
42  			return v;
43  		return src.convert(dst, v);
44  	}
45  
46  	private static SpinnerNumberModel rescale(final Unit src, final Unit dst,
47  			final SpinnerNumberModel m) {
48  		if (m == null || dst == null || src == null || src == dst)
49  			return m;
50  		if (log.isDebugEnabled())
51  			log.debug("Convert " + src + " -> " + dst + ":" + m.getValue()
52  					+ " -> " + src.convert(dst, m.getNumber().doubleValue()));
53  		m.setValue(src.convert(dst, m.getNumber().doubleValue()));
54  		m.setMinimum(src.convert(dst, ((Number) m.getMinimum()).doubleValue()));
55  		m.setMaximum(src.convert(dst, ((Number) m.getMaximum()).doubleValue()));
56  		return m;
57  	}
58  
59  	private Unit base = null;
60  	private final transient ChangeSupport change = new ChangeSupport(this);
61  	private Unit[] choose = new Unit[0];
62  	private final JComboBox combo;
63  	private final JLabel label;
64  	transient private double oldValueBase = Double.NaN;
65  	private Unit unit = null;
66  
67  	private final JSpinner value;
68  
69  	private boolean valueIsAdjusting;
70  
71  	public JSpinnerNumberUnit() {
72  		setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
73  		add(label = new JLabel("xxx"));
74  		add(value = new JSpinner());
75  		add(combo = new JComboBox(choose));
76  		combo.addItemListener(this);
77  		setBase(Unit.NONE);
78  		setChoose();
79  	}
80  
81  	/** Register for {@link #getValue()} changes. */
82  	public void addChangeListener(final ChangeListener l) {
83  		change.addChangeListener(l);
84  	}
85  
86  	/** internal use only! */
87  	public void focusGained(final FocusEvent e) {
88  		setValueIsAdjusting(true);
89  	}
90  
91  	/** internal use only! */
92  	public void focusLost(final FocusEvent e) {
93  		setValueIsAdjusting(false);
94  	}
95  
96  	/** {@link #getValue()} listeners. */
97  	public ChangeListener[] getChangeListeners() {
98  		return change.getChangeListeners();
99  	}
100 
101 	private DefaultEditor getEd() {
102 		return (JSpinner.DefaultEditor) value.getEditor();
103 	}
104 
105 	/** Allow custom layouting */
106 	JComponent getLabel() {
107 		return label;
108 	}
109 
110 	/** The data model according to {@link #getUnit()}. */
111 	public SpinnerNumberModel getModel() {
112 		return (SpinnerNumberModel) value.getModel();
113 	}
114 
115 	/** Allow custom layouting */
116 	JComponent getSpinner() {
117 		return value;
118 	}
119 
120 	/** Currently active {@link Unit} */
121 	public Unit getUnit() {
122 		if (true)
123 			return unit;
124 		else {
125 			if (!combo.isVisible())
126 				return Unit.NONE;
127 			final Unit old = (Unit) combo.getSelectedItem();
128 			if (old == null)
129 				return Unit.NONE;
130 			return old;
131 		}
132 	}
133 
134 	/** Allow custom layouting */
135 	JComponent getUnitCombo() {
136 		return combo;
137 	}
138 
139 	/** The current value in according to the {@link #setBase(Unit)} unit. */
140 	public double getValue() {
141 		return convert(getModel().getNumber().doubleValue(), getUnit(), base);
142 	}
143 
144 	/**
145 	 * Is {@link #getValue()} currently changing? ({@link JSpinner} has the
146 	 * focus)
147 	 */
148 	public boolean getValueIsAdjusting() {
149 		return change.getValueIsAdjusting();
150 	}
151 
152 	/** internal use only. */
153 	public void itemStateChanged(final ItemEvent e) {
154 		if (e.getSource() == combo)
155 			setUnit((Unit) e.getItem());
156 	}
157 
158 	/** Deregister for {@link #getValue()} changes. */
159 	public void removeChangeListener(final ChangeListener l) {
160 		change.removeChangeListener(l);
161 	}
162 
163 	/** The unit for {@link #getValue()}. TODO Check compatibility with choose. */
164 	public void setBase(final Unit base) {
165 		final Unit old = this.base;
166 		if (old == base)
167 			return;
168 		if (choose == null || choose.length == 0)
169 			setChoose(base);
170 		firePropertyChange("base", old, this.base = base);
171 	}
172 
173 	/**
174 	 * Alternative units.TODO Check compatibility with base.
175 	 * 
176 	 * @param choose
177 	 *            An empty list makes the {@link Unit} {@link JComboBox}
178 	 *            invisible.
179 	 */
180 	public void setChoose(final Unit... choose) {
181 		this.choose = choose;
182 		combo.setModel(new DefaultComboBoxModel(this.choose));
183 		combo.setVisible(this.choose != null && this.choose.length > 0);
184 		combo.setEnabled(combo.isVisible() && choose.length > 1);
185 		unit = (Unit) combo.getSelectedItem();
186 	}
187 
188 	@Override
189 	public void setEnabled(boolean enabled) {
190 		if (!enabled) {
191 			label.setEnabled(enabled);
192 			value.setEnabled(enabled);
193 			combo.setEnabled(enabled);
194 		} else {
195 			label.setEnabled(label.isVisible());
196 			value.setEnabled(value.isVisible());
197 			combo.setEnabled(combo.isVisible());
198 		}
199 		super.setEnabled(enabled);
200 	}
201 
202 	/**
203 	 * @param label
204 	 *            <code>null</code> makes the text {@link JLabel} to
205 	 *            invisible.
206 	 */
207 	public void setLabel(final String label) {
208 		final String old = this.label.getText();
209 		if (old == label)
210 			return;
211 		this.label.setText(label);
212 		this.label.setVisible(label != null);
213 		firePropertyChange("label", old, label);
214 	}
215 
216 	/**
217 	 * Mostly to set minimum and maximum values. Must be given in
218 	 * {@link #setBase(Unit)} units and is immediately converted to
219 	 * {@link #getUnit()}.
220 	 * 
221 	 * @see #rescale(Unit, Unit, SpinnerNumberModel)
222 	 * @param model
223 	 *            <code>null</code> makes the {@link JSpinner} invisible.
224 	 */
225 	public void setModel(final SpinnerNumberModel model) {
226 		final SpinnerNumberModel old = getModel();
227 		if (old == model)
228 			return;
229 		if (old != null) {
230 			// wire down the listener to set valueIsAdjusting
231 			getEd().getTextField().removeFocusListener(this);
232 			// model changes
233 			old.removeChangeListener(this);
234 		}
235 		oldValueBase = Double.NaN;
236 		if (model != null) {
237 			value.setModel(rescale(base, getUnit(), model));
238 			oldValueBase = getValue();
239 			// wire up the listener to set valueIsAdjusting
240 			getEd().getTextField().addFocusListener(this);
241 			// model changes
242 			model.addChangeListener(this);
243 		}
244 		value.setVisible(model != null);
245 		firePropertyChange("model", old, model);
246 	}
247 
248 	/** Set the currently active (display) {@link Unit} */
249 	public void setUnit(final Unit current) {
250 		final Unit old = unit;
251 		if (current == old)
252 			return;
253 		combo.setSelectedItem(unit = current);
254 		// re-adjust the model
255 		rescale(old, unit, getModel());
256 		firePropertyChange("unit", old, unit);
257 	}
258 
259 	public void setValue(double value) {
260 		if (oldValueBase == value)
261 			return;
262 		// convert to model:
263 		value = convert(value, base, getUnit());
264 		// re-convert to base to avoid rounding errors:
265 		oldValueBase = convert(value, getUnit(), base);
266 		if (value == getModel().getNumber().doubleValue())
267 			return;
268 		this.value.setValue(value);
269 	}
270 
271 	/** Internal use only! */
272 	public void setValueIsAdjusting(final boolean valueIsAdjusting) {
273 		final boolean old = this.valueIsAdjusting;
274 		if (old == valueIsAdjusting)
275 			return;
276 		firePropertyChange("valueIsAdjusting", old,
277 				this.valueIsAdjusting = valueIsAdjusting);
278 	}
279 
280 	/** internal use only! */
281 	public void stateChanged(final ChangeEvent e) {
282 		if (e.getSource() == getModel()) {
283 			final double v = getValue();
284 			if (oldValueBase == v)
285 				return;
286 			oldValueBase = v;
287 			change.fireStateChanged();
288 		}
289 	}
290 }