001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2001, Institut de Recherche pour le Développement
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.gui.swing.referencing;
018:
019: // Swing (for JSpinner)
020: import javax.swing.JSpinner;
021: import javax.swing.SpinnerModel;
022: import javax.swing.SpinnerNumberModel;
023: import javax.swing.JFormattedTextField;
024: import javax.swing.AbstractSpinnerModel;
025: import javax.swing.text.InternationalFormatter;
026: import javax.swing.text.DefaultFormatterFactory;
027:
028: // Other J2SE dependencies
029: import java.io.Serializable;
030: import java.text.ParseException;
031:
032: // Geotools dependencies
033: import org.geotools.measure.Angle;
034: import org.geotools.measure.Latitude;
035: import org.geotools.measure.Longitude;
036: import org.geotools.measure.AngleFormat;
037:
038: // Resources
039: import org.geotools.resources.Utilities;
040: import org.geotools.resources.i18n.Errors;
041: import org.geotools.resources.i18n.ErrorKeys;
042:
043: /**
044: * A {@link SpinnerModel} for sequences of angles.
045: * This model work like {@link SpinnerNumberModel}.
046: *
047: * @see JSpinner
048: * @see SpinnerNumberModel
049: *
050: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/extension/widgets-swing/src/main/java/org/geotools/gui/swing/referencing/SpinnerAngleModel.java $
051: * @version $Id: SpinnerAngleModel.java 20883 2006-08-07 13:48:09Z jgarnett $
052: * @author Adapted from Hans Muller
053: * @author Martin Desruisseaux
054: */
055: final class SpinnerAngleModel extends AbstractSpinnerModel implements
056: Serializable {
057: /**
058: * The current value.
059: */
060: private Angle value;
061:
062: /**
063: * The minimum and maximum values.
064: */
065: private double minimum, maximum;
066:
067: /**
068: * The step size.
069: */
070: private double stepSize = 1;
071:
072: /**
073: * Constructs a {@code SpinnerAngleModel} that represents a closed sequence of angles.
074: * Initial minimum and maximum values are choosen according the {@code value} type:
075: *
076: * <table>
077: * <tr><td>{@link Longitude} </td> <td>-180° to 180°</td></tr>
078: * <tr><td>{@link Latitude} </td> <td>-90° to 90°</td> </tr>
079: * <tr><td>{@link Angle} </td> <td>0° to 360°</td> </tr>
080: * </table>
081: *
082: * @param value the current (non {@code null}) value of the model
083: * @throws IllegalArgumentException if {@code value} is null.
084: */
085: public SpinnerAngleModel(final Angle value) {
086: this .value = value;
087: if (value instanceof Longitude) {
088: minimum = Longitude.MIN_VALUE;
089: maximum = Longitude.MAX_VALUE;
090: } else if (value instanceof Latitude) {
091: minimum = Latitude.MIN_VALUE;
092: maximum = Latitude.MAX_VALUE;
093: } else if (value != null) {
094: minimum = 0;
095: maximum = 360;
096: } else {
097: throw new IllegalArgumentException();
098: }
099: }
100:
101: /**
102: * Changes the lower bound for angles in this sequence.
103: */
104: public void setMinimum(final double minimum) {
105: if (this .minimum != minimum) {
106: this .minimum = minimum;
107: fireStateChanged();
108: }
109: }
110:
111: /**
112: * Returns the first angle in this sequence.
113: */
114: public double getMinimum() {
115: return minimum;
116: }
117:
118: /**
119: * Changes the upper bound for angles in this sequence.
120: */
121: public void setMaximum(final double maximum) {
122: if (this .maximum != maximum) {
123: this .maximum = maximum;
124: fireStateChanged();
125: }
126: }
127:
128: /**
129: * Returns the last angle in the sequence.
130: */
131: public double getMaximum() {
132: return maximum;
133: }
134:
135: /**
136: * Changes the size of the value change computed by the {@code getNextValue}
137: * and {@code getPreviousValue} methods.
138: */
139: public void setStepSize(final double stepSize) {
140: if (this .stepSize != stepSize) {
141: this .stepSize = stepSize;
142: fireStateChanged();
143: }
144: }
145:
146: /**
147: * Returns the size of the value change computed by the
148: * {@code getNextValue} and {@code getPreviousValue} methods.
149: */
150: public double getStepSize() {
151: return stepSize;
152: }
153:
154: /**
155: * Wrap the specified value into an {@link Angle} object.
156: */
157: final Angle toAngle(final double newValue) {
158: if (value instanceof Longitude)
159: return new Longitude(newValue);
160: if (value instanceof Latitude)
161: return new Latitude(newValue);
162: return new Angle(newValue);
163: }
164:
165: /**
166: * Returns {@code value + factor * stepSize}.
167: */
168: private Angle getNextValue(final int factor) {
169: final double newValue = value.degrees() + stepSize * factor;
170: if (!(newValue >= minimum && newValue <= maximum))
171: return null;
172: return toAngle(newValue);
173: }
174:
175: /**
176: * Returns the next angle in the sequence.
177: */
178: public Object getNextValue() {
179: return getNextValue(+1);
180: }
181:
182: /**
183: * Returns the previous angle in the sequence.
184: */
185: public Object getPreviousValue() {
186: return getNextValue(-1);
187: }
188:
189: /**
190: * Returns the value of the current angle of the sequence.
191: */
192: public Object getValue() {
193: return value;
194: }
195:
196: /**
197: * Sets the current value for this sequence.
198: */
199: public void setValue(final Object value) {
200: if (!(value instanceof Angle)) {
201: throw new IllegalArgumentException(Errors.format(
202: ErrorKeys.ILLEGAL_ARGUMENT_$2, "value", value));
203: }
204: if (!Utilities.equals(value, this .value)) {
205: this .value = (Angle) value;
206: fireStateChanged();
207: }
208: }
209:
210: /**
211: * This subclass of {@link javax.swing.InternationalFormatter} maps the
212: * minimum/maximum properties to a {@link SpinnerAngleModel}.
213: *
214: * @version $Id: SpinnerAngleModel.java 20883 2006-08-07 13:48:09Z jgarnett $
215: * @author Adapted from Hans Muller
216: * @author Martin Desruisseaux
217: */
218: private static class EditorFormatter extends InternationalFormatter {
219: /**
220: * The spinner model.
221: */
222: private final SpinnerAngleModel model;
223:
224: /**
225: * Construct a formatter.
226: */
227: EditorFormatter(final SpinnerAngleModel model,
228: final AngleFormat format) {
229: super (format);
230: this .model = model;
231: setAllowsInvalid(true);
232: setCommitsOnValidEdit(false);
233: setOverwriteMode(false);
234:
235: final Class classe;
236: final Object value = model.getValue();
237: if (value instanceof Longitude)
238: classe = Longitude.class;
239: else if (value instanceof Latitude)
240: classe = Latitude.class;
241: else
242: classe = Angle.class;
243: setValueClass(classe);
244: }
245:
246: /**
247: * Returns the {@link Object} representation of the {@link String} {@code text}.
248: */
249: public Object stringToValue(final String text)
250: throws ParseException {
251: final Object value = super .stringToValue(text);
252: if (value instanceof Longitude)
253: return value;
254: if (value instanceof Latitude)
255: return value;
256: if (value instanceof Angle) {
257: final Class valueClass = getValueClass();
258: if (Longitude.class.isAssignableFrom(valueClass)) {
259: return new Longitude(((Angle) value).degrees());
260: }
261: if (Latitude.class.isAssignableFrom(valueClass)) {
262: return new Latitude(((Angle) value).degrees());
263: }
264: }
265: return value;
266: }
267:
268: /**
269: * Sets the minimum value.
270: */
271: public void setMinimum(final Comparable min) {
272: model.setMinimum(((Angle) min).degrees());
273: }
274:
275: /**
276: * Gets the minimum value.
277: */
278: public Comparable getMinimum() {
279: return model.toAngle(model.getMinimum());
280: }
281:
282: /**
283: * Sets the maximum value.
284: */
285: public void setMaximum(final Comparable max) {
286: model.setMaximum(((Angle) max).degrees());
287: }
288:
289: /**
290: * Gets the maximum value.
291: */
292: public Comparable getMaximum() {
293: return model.toAngle(model.getMaximum());
294: }
295: }
296:
297: /**
298: * An editor for a {@link javax.swing.JSpinner}. The value of the editor is
299: * displayed with a {@link javax.swing.JFormattedTextField} whose format is
300: * defined by a {@link javax.swing.text.InternationalFormatter} instance
301: * whose minimum and maximum properties are mapped to the
302: * {@link SpinnerNumberModel}.
303: *
304: * @version $Id: SpinnerAngleModel.java 20883 2006-08-07 13:48:09Z jgarnett $
305: * @author Adapted from Hans Muller
306: * @author Martin Desruisseaux
307: */
308: final static class Editor extends JSpinner.DefaultEditor {
309: /**
310: * Construct an editor for the specified format.
311: */
312: public Editor(final JSpinner spinner, final AngleFormat format) {
313: super (spinner);
314: final SpinnerModel genericModel = spinner.getModel();
315: if (!(genericModel instanceof SpinnerAngleModel)) {
316: throw new IllegalArgumentException();
317: }
318: final SpinnerAngleModel model = (SpinnerAngleModel) genericModel;
319: final EditorFormatter formatter = new EditorFormatter(
320: model, format);
321: final DefaultFormatterFactory factory = new DefaultFormatterFactory(
322: formatter);
323: final JFormattedTextField field = getTextField();
324: field.setEditable(true);
325: field.setFormatterFactory(factory);
326: field.setHorizontalAlignment(JFormattedTextField.RIGHT);
327:
328: /* TODO - initializing the column width of the text field
329: * is imprecise and doing it here is tricky because
330: * the developer may configure the formatter later.
331: */
332: try {
333: final String maxString = formatter
334: .valueToString(formatter.getMinimum());
335: final String minString = formatter
336: .valueToString(formatter.getMaximum());
337: field.setColumns(Math.max(maxString.length(), minString
338: .length()));
339: } catch (ParseException exception) {
340: // TODO should throw a chained error here
341: }
342: }
343: }
344: }
|