001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-present, by David Gilbert and Contributors.
006 *
007 * Project Info:  http://www.jfree.org/jfreechart/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
022 * USA.
023 *
024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * -------------------
028 * TextAnnotation.java
029 * -------------------
030 * (C) Copyright 2002-present, by David Gilbert and Contributors.
031 *
032 * Original Author:  David Gilbert;
033 * Contributor(s):   Peter Kolb (patch 2809117);
034 *                   Martin Hoeller;
035 *                   Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier);
036 * 
037 */
038
039package org.jfree.chart.annotations;
040
041import java.awt.Color;
042import java.awt.Font;
043import java.awt.Paint;
044import java.io.IOException;
045import java.io.ObjectInputStream;
046import java.io.ObjectOutputStream;
047import java.io.Serializable;
048import java.util.Objects;
049
050import org.jfree.chart.HashUtils;
051import org.jfree.chart.event.AnnotationChangeEvent;
052import org.jfree.chart.ui.TextAnchor;
053import org.jfree.chart.util.PaintUtils;
054import org.jfree.chart.util.Args;
055import org.jfree.chart.util.SerialUtils;
056
057/**
058 * A base class for text annotations.  This class records the content but not
059 * the location of the annotation.
060 */
061public class TextAnnotation extends AbstractAnnotation implements Serializable {
062
063    /** For serialization. */
064    private static final long serialVersionUID = 7008912287533127432L;
065
066    /** The default font. */
067    public static final Font DEFAULT_FONT
068            = new Font("SansSerif", Font.PLAIN, 10);
069
070    /** The default paint. */
071    public static final Paint DEFAULT_PAINT = Color.BLACK;
072
073    /** The default text anchor. */
074    public static final TextAnchor DEFAULT_TEXT_ANCHOR = TextAnchor.CENTER;
075
076    /** The default rotation anchor. */
077    public static final TextAnchor DEFAULT_ROTATION_ANCHOR = TextAnchor.CENTER;
078
079    /** The default rotation angle. */
080    public static final double DEFAULT_ROTATION_ANGLE = 0.0;
081
082    /** The text. */
083    private String text;
084
085    /** The font. */
086    private Font font;
087
088    /** The paint. */
089    private transient Paint paint;
090
091    /** The text anchor. */
092    private TextAnchor textAnchor;
093
094    /** The rotation anchor. */
095    private TextAnchor rotationAnchor;
096
097    /** The rotation angle. */
098    private double rotationAngle;
099
100    /**
101     * Creates a text annotation with default settings.
102     *
103     * @param text  the text ({@code null} not permitted).
104     */
105    protected TextAnnotation(String text) {
106        super();
107        Args.nullNotPermitted(text, "text");
108        this.text = text;
109        this.font = DEFAULT_FONT;
110        this.paint = DEFAULT_PAINT;
111        this.textAnchor = DEFAULT_TEXT_ANCHOR;
112        this.rotationAnchor = DEFAULT_ROTATION_ANCHOR;
113        this.rotationAngle = DEFAULT_ROTATION_ANGLE;
114    }
115
116    /**
117     * Returns the text for the annotation.
118     *
119     * @return The text (never {@code null}).
120     *
121     * @see #setText(String)
122     */
123    public String getText() {
124        return this.text;
125    }
126
127    /**
128     * Sets the text for the annotation and sends an 
129     * {@link AnnotationChangeEvent} to all registered listeners.
130     *
131     * @param text  the text ({@code null} not permitted).
132     *
133     * @see #getText()
134     */
135    public void setText(String text) {
136        Args.nullNotPermitted(text, "text");
137        this.text = text;
138        fireAnnotationChanged();
139    }
140
141    /**
142     * Returns the font for the annotation.
143     *
144     * @return The font (never {@code null}).
145     *
146     * @see #setFont(Font)
147     */
148    public Font getFont() {
149        return this.font;
150    }
151
152    /**
153     * Sets the font for the annotation and sends an
154     * {@link AnnotationChangeEvent} to all registered listeners.
155     *
156     * @param font  the font ({@code null} not permitted).
157     *
158     * @see #getFont()
159     */
160    public void setFont(Font font) {
161        Args.nullNotPermitted(font, "font");
162        this.font = font;
163        fireAnnotationChanged();
164    }
165
166    /**
167     * Returns the paint for the annotation.
168     *
169     * @return The paint (never {@code null}).
170     *
171     * @see #setPaint(Paint)
172     */
173    public Paint getPaint() {
174        return this.paint;
175    }
176
177    /**
178     * Sets the paint for the annotation and sends an
179     * {@link AnnotationChangeEvent} to all registered listeners.
180     *
181     * @param paint  the paint ({@code null} not permitted).
182     *
183     * @see #getPaint()
184     */
185    public void setPaint(Paint paint) {
186        Args.nullNotPermitted(paint, "paint");
187        this.paint = paint;
188        fireAnnotationChanged();
189    }
190
191    /**
192     * Returns the text anchor.
193     *
194     * @return The text anchor.
195     *
196     * @see #setTextAnchor(TextAnchor)
197     */
198    public TextAnchor getTextAnchor() {
199        return this.textAnchor;
200    }
201
202    /**
203     * Sets the text anchor (the point on the text bounding rectangle that is
204     * aligned to the (x, y) coordinate of the annotation) and sends an
205     * {@link AnnotationChangeEvent} to all registered listeners.
206     *
207     * @param anchor  the anchor point ({@code null} not permitted).
208     *
209     * @see #getTextAnchor()
210     */
211    public void setTextAnchor(TextAnchor anchor) {
212        Args.nullNotPermitted(anchor, "anchor");
213        this.textAnchor = anchor;
214        fireAnnotationChanged();
215    }
216
217    /**
218     * Returns the rotation anchor.
219     *
220     * @return The rotation anchor point (never {@code null}).
221     *
222     * @see #setRotationAnchor(TextAnchor)
223     */
224    public TextAnchor getRotationAnchor() {
225        return this.rotationAnchor;
226    }
227
228    /**
229     * Sets the rotation anchor point and sends an
230     * {@link AnnotationChangeEvent} to all registered listeners.
231     *
232     * @param anchor  the anchor ({@code null} not permitted).
233     *
234     * @see #getRotationAnchor()
235     */
236    public void setRotationAnchor(TextAnchor anchor) {
237        Args.nullNotPermitted(anchor, "anchor");
238        this.rotationAnchor = anchor;
239        fireAnnotationChanged();
240    }
241
242    /**
243     * Returns the rotation angle in radians.
244     *
245     * @return The rotation angle.
246     *
247     * @see #setRotationAngle(double)
248     */
249    public double getRotationAngle() {
250        return this.rotationAngle;
251    }
252
253    /**
254     * Sets the rotation angle and sends an {@link AnnotationChangeEvent} to
255     * all registered listeners.  The angle is measured clockwise in radians.
256     *
257     * @param angle  the angle (in radians).
258     *
259     * @see #getRotationAngle()
260     */
261    public void setRotationAngle(double angle) {
262        this.rotationAngle = angle;
263        fireAnnotationChanged();
264    }
265
266    /**
267     * Tests this object for equality with an arbitrary object.
268     *
269     * @param obj  the object ({@code null} permitted).
270     *
271     * @return {@code true} or {@code false}.
272     */
273    @Override
274    public boolean equals(Object obj) {
275        if (obj == this) {
276            return true;
277        }
278        // now try to reject equality...
279        if (!(obj instanceof TextAnnotation)) {
280            return false;
281        }
282        TextAnnotation that = (TextAnnotation) obj;
283        if (!Objects.equals(this.text, that.getText())) {
284            return false;
285        }
286        if (!Objects.equals(this.font, that.getFont())) {
287            return false;
288        }
289        if (!PaintUtils.equal(this.paint, that.getPaint())) {
290            return false;
291        }
292        if (!Objects.equals(this.textAnchor, that.getTextAnchor())) {
293            return false;
294        }
295        if (!Objects.equals(this.rotationAnchor, that.getRotationAnchor())) {
296            return false;
297        }
298        if (Double.doubleToLongBits(this.rotationAngle) !=
299            Double.doubleToLongBits(that.rotationAngle)) {
300            return false;
301        }
302
303        // fix the "equals not symmetric" problem
304        if (!that.canEqual(this)) {
305            return false;
306        }
307
308        return super.equals(obj);
309    }
310
311    /**
312     * Ensures symmetry between super/subclass implementations of equals. For
313     * more detail, see http://jqno.nl/equalsverifier/manual/inheritance.
314     *
315     * @param other Object
316     * 
317     * @return true ONLY if the parameter is THIS class type
318     */
319    @Override
320    public boolean canEqual(Object other) {
321        // fix the "equals not symmetric" problem
322        return (other instanceof TextAnnotation);
323    }
324
325    /**
326     * Returns a hash code for this instance.
327     *
328     * @return A hash code.
329     */
330    @Override
331    public int hashCode() {
332        int result = super.hashCode(); // equals calls superclass, hashCode must also
333        result = 37 * result + Objects.hashCode(this.font);
334        result = 37 * result + HashUtils.hashCodeForPaint(this.paint);
335        result = 37 * result + Objects.hashCode(this.rotationAnchor);
336        long temp = Double.doubleToLongBits(this.rotationAngle);
337        result = 37 * result + (int) (temp ^ (temp >>> 32));
338        result = 37 * result + Objects.hashCode(this.text);
339        result = 37 * result + Objects.hashCode(this.textAnchor);
340        return result;
341    }
342
343    /**
344     * Provides serialization support.
345     *
346     * @param stream  the output stream.
347     *
348     * @throws IOException if there is an I/O error.
349     */
350    private void writeObject(ObjectOutputStream stream) throws IOException {
351        stream.defaultWriteObject();
352        SerialUtils.writePaint(this.paint, stream);
353    }
354
355    /**
356     * Provides serialization support.
357     *
358     * @param stream  the input stream.
359     *
360     * @throws IOException  if there is an I/O error.
361     * @throws ClassNotFoundException  if there is a classpath problem.
362     */
363    private void readObject(ObjectInputStream stream)
364        throws IOException, ClassNotFoundException {
365        stream.defaultReadObject();
366        this.paint = SerialUtils.readPaint(stream);
367    }
368
369}