001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2000, Institut de Recherche pour le Développement
006: * (C) 1999, Pêches et Océans Canada
007: *
008: * This library is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU Lesser General Public
010: * License as published by the Free Software Foundation; either
011: * version 2.1 of the License, or (at your option) any later version.
012: *
013: * This library is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
016: * Lesser General Public License for more details.
017: */
018: package org.geotools.axis;
019:
020: // J2SE dependencies
021: import java.awt.RenderingHints;
022: import java.beans.PropertyChangeEvent;
023: import java.beans.PropertyChangeListener;
024: import java.beans.PropertyChangeSupport;
025: import java.io.Serializable;
026: import java.util.Locale;
027:
028: // Units dependencies
029: import javax.units.Unit;
030: import javax.units.ConversionException;
031:
032: // Geotools dependencies
033: import org.geotools.resources.Utilities;
034: import org.geotools.resources.i18n.Errors;
035: import org.geotools.resources.i18n.ErrorKeys;
036:
037: /**
038: * Base class for graduation.
039: *
040: * @since 2.0
041: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/extension/widgets-swing/src/main/java/org/geotools/axis/AbstractGraduation.java $
042: * @version $Id: AbstractGraduation.java 27576 2007-10-22 13:54:24Z desruisseaux $
043: * @author Martin Desruisseaux
044: */
045: public abstract class AbstractGraduation implements Graduation,
046: Serializable {
047: /**
048: * Serial number for interoperability with different versions.
049: */
050: private static final long serialVersionUID = 5215728323932315112L;
051:
052: /**
053: * The axis's units, or {@code null} if unknow.
054: */
055: private Unit unit;
056:
057: /**
058: * The axis title for this graduation.
059: */
060: private String title;
061:
062: /**
063: * The locale for formatting labels.
064: */
065: private Locale locale = Locale.getDefault();
066:
067: /**
068: * A list of event listeners for this component.
069: */
070: protected final PropertyChangeSupport listenerList;
071:
072: /**
073: * Constructs a graduation with the supplied units.
074: *
075: * @param unit The axis's units, or {@code null} if unknow.
076: */
077: public AbstractGraduation(final Unit unit) {
078: listenerList = new PropertyChangeSupport(this );
079: this .unit = unit;
080: }
081:
082: /**
083: * Sets the minimum value for this graduation. If the new minimum is greater than the current
084: * maximum, then the maximum will also be set to a value greater than or equals to the minimum.
085: *
086: * @param value The new minimum in {@link #getUnit} units.
087: * @return {@code true} if the state of this graduation changed as a result of this call, or
088: * {@code false} if the new value is identical to the previous one.
089: * @throws IllegalArgumentException If {@code value} is NaN ou infinite.
090: *
091: * @see #getMinimum
092: * @see #setMaximum(double)
093: */
094: public abstract boolean setMinimum(final double value)
095: throws IllegalArgumentException;
096:
097: /**
098: * Sets the maximum value for this graduation. If the new maximum is less than the current
099: * minimum, then the minimum will also be set to a value less than or equals to the maximum.
100: *
101: * @param value The new maximum in {@link #getUnit} units.
102: * @return {@code true} if the state of this graduation changed as a result of this call, or
103: * {@code false} if the new value is identical to the previous one.
104: * @throws IllegalArgumentException If {@code value} is NaN ou infinite.
105: *
106: * @see #getMaximum
107: * @see #setMinimum(double)
108: */
109: public abstract boolean setMaximum(final double value)
110: throws IllegalArgumentException;
111:
112: /**
113: * Returns the axis title. If {@code includeUnits} is {@code true}, then the returned string
114: * will includes units as in "Temperature (°C)". The exact formatting is local-dependent.
115: *
116: * @param includeSymbol {@code true} to format unit symbol after the name.
117: * @return The graduation name (also to be use as axis title).
118: */
119: public synchronized String getTitle(final boolean includeSymbol) {
120: if (includeSymbol) {
121: final String symbol = getSymbol();
122: if (symbol != null && symbol.length() != 0) {
123: // TODO: localize if needed.
124: return (title != null) ? title + " (" + symbol + ')'
125: : symbol;
126: }
127: }
128: return title;
129: }
130:
131: /**
132: * Sets the axis title, not including unit symbol. This method will fire a
133: * property change event with the {@code "title"} property name.
134: *
135: * @param title New axis title, or {@code null} to remove any previous setting.
136: */
137: public void setTitle(final String title) {
138: final String old;
139: synchronized (this ) {
140: old = this .title;
141: this .title = title;
142: }
143: listenerList.firePropertyChange("title", old, title);
144: }
145:
146: /**
147: * Returns a string representation of axis's units, or {@code null}
148: * if there is none. The default implementation returns the string
149: * representation of {@link #getUnit}.
150: */
151: String getSymbol() {
152: final Unit unit = getUnit();
153: return (unit != null) ? unit.toString() : null;
154: }
155:
156: /**
157: * Returns the graduation's units, or {@code null} if unknow.
158: */
159: public Unit getUnit() {
160: return unit;
161: }
162:
163: /**
164: * Changes the graduation's units. Subclasses will automatically convert minimum and maximum
165: * values from the old units to the new one. This method fires a property change event with the
166: * {@code "unit"} property name.
167: *
168: * @param unit The new units, or {@code null} if unknow. If null, minimum and maximum values
169: * are not converted.
170: * @throws ConversionException if units are not convertible, or if the
171: * specified units is illegal for this graduation.
172: */
173: public void setUnit(final Unit unit) throws ConversionException {
174: final Unit oldUnit;
175: synchronized (this ) {
176: oldUnit = this .unit;
177: this .unit = unit;
178: }
179: listenerList.firePropertyChange("unit", oldUnit, unit);
180: }
181:
182: /**
183: * Returns the locale to use for formatting labels.
184: */
185: public Locale getLocale() {
186: return locale;
187: }
188:
189: /**
190: * Sets the locale to use for formatting labels.
191: * This will fire a property change event with the {@code "locale"} property name.
192: */
193: public synchronized void setLocale(final Locale locale) {
194: final Locale old;
195: synchronized (this ) {
196: old = this .locale;
197: this .locale = locale;
198: }
199: listenerList.firePropertyChange("locale", old, locale);
200: }
201:
202: /**
203: * Adds a {@link PropertyChangeListener} to the listener list. The listener is
204: * registered for all properties. A {@link PropertyChangeEvent} will get fired
205: * in response to setting a property, such as {@link #setTitle} or {@link #setLocale}.
206: */
207: public void addPropertyChangeListener(
208: final PropertyChangeListener listener) {
209: listenerList.addPropertyChangeListener(listener);
210: }
211:
212: /**
213: * Removes a {@link PropertyChangeListener} from the listener list.
214: */
215: public void removePropertyChangeListener(
216: final PropertyChangeListener listener) {
217: listenerList.removePropertyChangeListener(listener);
218: }
219:
220: /**
221: * Retourne la longueur de l'axe, en pixels ou en points (1/72 de pouce).
222: */
223: static float getVisualAxisLength(final RenderingHints hints) {
224: return getValue(hints, VISUAL_AXIS_LENGTH, 600);
225: }
226:
227: /**
228: * Retourne l'espace approximatif (en pixels ou en points) à laisser entre les
229: * graduations principales. L'espace réel entre les graduations peut être légèrement
230: * différent, par exemple pour avoir des étiquettes qui correspondent à des valeurs
231: * arrondies.
232: */
233: static float getVisualTickSpacing(final RenderingHints hints) {
234: return getValue(hints, VISUAL_TICK_SPACING, 48);
235: }
236:
237: /**
238: * Retourne une valeur sous forme de nombre réel.
239: */
240: private static float getValue(final RenderingHints hints,
241: final RenderingHints.Key key, final float defaultValue) {
242: if (hints != null) {
243: final Object object = hints.get(key);
244: if (object instanceof Number) {
245: final float value = ((Number) object).floatValue();
246: if (value != 0 && !Float.isInfinite(value)) {
247: return value;
248: }
249: }
250: }
251: return defaultValue;
252: }
253:
254: /**
255: * Vérifie que le nombre spécifié est non-nul. S'il
256: * est 0, NaN ou infini, une exception sera lancée.
257: *
258: * @param name Nom de l'argument.
259: * @param n Nombre à vérifier.
260: * @throws IllegalArgumentException Si <var>n</var> est NaN ou infini.
261: */
262: static void ensureNonNull(final String name, final double n)
263: throws IllegalArgumentException {
264: if (Double.isNaN(n) || Double.isInfinite(n) || n == 0) {
265: throw new IllegalArgumentException(Errors.format(
266: ErrorKeys.ILLEGAL_ARGUMENT_$2, name, new Double(n)));
267: }
268: }
269:
270: /**
271: * Vérifie que le nombre spécifié est réel. S'il est NaN ou infini, une exception sera lancée.
272: *
273: * @param name Nom de l'argument.
274: * @param n Nombre à vérifier.
275: * @throws IllegalArgumentException Si <var>n</var> est NaN ou infini.
276: */
277: static void ensureFinite(final String name, final double n)
278: throws IllegalArgumentException {
279: if (Double.isNaN(n) || Double.isInfinite(n)) {
280: throw new IllegalArgumentException(Errors.format(
281: ErrorKeys.ILLEGAL_ARGUMENT_$2, name, new Double(n)));
282: }
283: }
284:
285: /**
286: * Vérifie que le nombre spécifié est réel. S'il est NaN ou infini, une exception sera lancée.
287: *
288: * @param name Nom de l'argument.
289: * @param n Nombre à vérifier.
290: * @throws IllegalArgumentException Si <var>n</var> est NaN ou infini.
291: */
292: static void ensureFinite(final String name, final float n)
293: throws IllegalArgumentException {
294: if (Float.isNaN(n) || Float.isInfinite(n)) {
295: throw new IllegalArgumentException(Errors.format(
296: ErrorKeys.ILLEGAL_ARGUMENT_$2, name, new Float(n)));
297: }
298: }
299:
300: /**
301: * Compares this graduation with the specified object for equality.
302: * This method do not compare listeners registered in {@link #listenerList}.
303: */
304: public boolean equals(final Object object) {
305: if (object != null && object.getClass().equals(getClass())) {
306: final AbstractGraduation that = (AbstractGraduation) object;
307: return Utilities.equals(this .unit, that.unit)
308: && Utilities.equals(this .title, that.title)
309: && Utilities.equals(this .locale, that.locale);
310: }
311: return false;
312: }
313:
314: /**
315: * Returns a hash value for this graduation.
316: */
317: public int hashCode() {
318: int code = (int) serialVersionUID;
319: if (title != null) {
320: code ^= title.hashCode();
321: }
322: if (unit != null) {
323: code ^= unit.hashCode();
324: }
325: return code;
326: }
327: }
|