001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2021, by Object Refinery Limited 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 * Title.java
029 * ----------
030 * (C) Copyright 2000-2021, by David Berry and Contributors.
031 *
032 * Original Author:  David Berry;
033 * Contributor(s):   David Gilbert (for Object Refinery Limited);
034 *                   Nicolas Brodu;
035 *
036 */
037
038package org.jfree.chart.title;
039
040import java.awt.Graphics2D;
041import java.awt.geom.Rectangle2D;
042import java.io.IOException;
043import java.io.ObjectInputStream;
044import java.io.ObjectOutputStream;
045import java.io.Serializable;
046
047import javax.swing.event.EventListenerList;
048
049import org.jfree.chart.block.AbstractBlock;
050import org.jfree.chart.block.Block;
051import org.jfree.chart.event.TitleChangeEvent;
052import org.jfree.chart.event.TitleChangeListener;
053import org.jfree.chart.ui.HorizontalAlignment;
054import org.jfree.chart.ui.RectangleEdge;
055import org.jfree.chart.ui.RectangleInsets;
056import org.jfree.chart.ui.VerticalAlignment;
057import org.jfree.chart.util.ObjectUtils;
058import org.jfree.chart.util.Args;
059
060/**
061 * The base class for all chart titles.  A chart can have multiple titles,
062 * appearing at the top, bottom, left or right of the chart.
063 * <P>
064 * Concrete implementations of this class will render text and images, and
065 * hence do the actual work of drawing titles.
066 */
067public abstract class Title extends AbstractBlock
068            implements Block, Cloneable, Serializable {
069
070    /** For serialization. */
071    private static final long serialVersionUID = -6675162505277817221L;
072
073    /** The default title position. */
074    public static final RectangleEdge DEFAULT_POSITION = RectangleEdge.TOP;
075
076    /** The default horizontal alignment. */
077    public static final HorizontalAlignment
078            DEFAULT_HORIZONTAL_ALIGNMENT = HorizontalAlignment.CENTER;
079
080    /** The default vertical alignment. */
081    public static final VerticalAlignment
082            DEFAULT_VERTICAL_ALIGNMENT = VerticalAlignment.CENTER;
083
084    /** Default title padding. */
085    public static final RectangleInsets DEFAULT_PADDING = new RectangleInsets(
086            1, 1, 1, 1);
087
088    /**
089     * A flag that controls whether or not the title is visible.
090     */
091    public boolean visible;
092
093    /** The title position. */
094    private RectangleEdge position;
095
096    /** The horizontal alignment of the title content. */
097    private HorizontalAlignment horizontalAlignment;
098
099    /** The vertical alignment of the title content. */
100    private VerticalAlignment verticalAlignment;
101
102    /** Storage for registered change listeners. */
103    private transient EventListenerList listenerList;
104
105    /**
106     * A flag that can be used to temporarily disable the listener mechanism.
107     */
108    private boolean notify;
109
110    /**
111     * Creates a new title, using default attributes where necessary.
112     */
113    protected Title() {
114        this(Title.DEFAULT_POSITION,
115                Title.DEFAULT_HORIZONTAL_ALIGNMENT,
116                Title.DEFAULT_VERTICAL_ALIGNMENT, Title.DEFAULT_PADDING);
117    }
118
119    /**
120     * Creates a new title, using default attributes where necessary.
121     *
122     * @param position  the position of the title ({@code null} not
123     *                  permitted).
124     * @param horizontalAlignment  the horizontal alignment of the title
125     *                             ({@code null} not permitted).
126     * @param verticalAlignment  the vertical alignment of the title
127     *                           ({@code null} not permitted).
128     */
129    protected Title(RectangleEdge position,
130                    HorizontalAlignment horizontalAlignment,
131                    VerticalAlignment verticalAlignment) {
132
133        this(position, horizontalAlignment, verticalAlignment,
134                Title.DEFAULT_PADDING);
135
136    }
137
138    /**
139     * Creates a new title.
140     *
141     * @param position  the position of the title ({@code null} not
142     *                  permitted).
143     * @param horizontalAlignment  the horizontal alignment of the title (LEFT,
144     *                             CENTER or RIGHT, {@code null} not
145     *                             permitted).
146     * @param verticalAlignment  the vertical alignment of the title (TOP,
147     *                           MIDDLE or BOTTOM, {@code null} not
148     *                           permitted).
149     * @param padding  the amount of space to leave around the outside of the
150     *                 title ({@code null} not permitted).
151     */
152    protected Title(RectangleEdge position, 
153            HorizontalAlignment horizontalAlignment, 
154            VerticalAlignment verticalAlignment, RectangleInsets padding) {
155
156        Args.nullNotPermitted(position, "position");
157        Args.nullNotPermitted(horizontalAlignment, "horizontalAlignment");
158        Args.nullNotPermitted(verticalAlignment, "verticalAlignment");
159        Args.nullNotPermitted(padding, "padding");
160
161        this.visible = true;
162        this.position = position;
163        this.horizontalAlignment = horizontalAlignment;
164        this.verticalAlignment = verticalAlignment;
165        setPadding(padding);
166        this.listenerList = new EventListenerList();
167        this.notify = true;
168    }
169
170    /**
171     * Returns a flag that controls whether or not the title should be
172     * drawn.  The default value is {@code true}.
173     *
174     * @return A boolean.
175     *
176     * @see #setVisible(boolean)
177     */
178    public boolean isVisible() {
179        return this.visible;
180    }
181
182    /**
183     * Sets a flag that controls whether or not the title should be drawn, and
184     * sends a {@link TitleChangeEvent} to all registered listeners.
185     *
186     * @param visible  the new flag value.
187     *
188     * @see #isVisible()
189     */
190    public void setVisible(boolean visible) {
191        this.visible = visible;
192        notifyListeners(new TitleChangeEvent(this));
193    }
194
195    /**
196     * Returns the position of the title.
197     *
198     * @return The title position (never {@code null}).
199     */
200    public RectangleEdge getPosition() {
201        return this.position;
202    }
203
204    /**
205     * Sets the position for the title and sends a {@link TitleChangeEvent} to
206     * all registered listeners.
207     *
208     * @param position  the position ({@code null} not permitted).
209     */
210    public void setPosition(RectangleEdge position) {
211        Args.nullNotPermitted(position, "position");
212        if (this.position != position) {
213            this.position = position;
214            notifyListeners(new TitleChangeEvent(this));
215        }
216    }
217
218    /**
219     * Returns the horizontal alignment of the title.
220     *
221     * @return The horizontal alignment (never {@code null}).
222     */
223    public HorizontalAlignment getHorizontalAlignment() {
224        return this.horizontalAlignment;
225    }
226
227    /**
228     * Sets the horizontal alignment for the title and sends a
229     * {@link TitleChangeEvent} to all registered listeners.
230     *
231     * @param alignment  the horizontal alignment ({@code null} not
232     *                   permitted).
233     */
234    public void setHorizontalAlignment(HorizontalAlignment alignment) {
235        Args.nullNotPermitted(alignment, "alignment");
236        if (this.horizontalAlignment != alignment) {
237            this.horizontalAlignment = alignment;
238            notifyListeners(new TitleChangeEvent(this));
239        }
240    }
241
242    /**
243     * Returns the vertical alignment of the title.
244     *
245     * @return The vertical alignment (never {@code null}).
246     */
247    public VerticalAlignment getVerticalAlignment() {
248        return this.verticalAlignment;
249    }
250
251    /**
252     * Sets the vertical alignment for the title, and notifies any registered
253     * listeners of the change.
254     *
255     * @param alignment  the new vertical alignment (TOP, MIDDLE or BOTTOM,
256     *                   {@code null} not permitted).
257     */
258    public void setVerticalAlignment(VerticalAlignment alignment) {
259        Args.nullNotPermitted(alignment, "alignment");
260        if (this.verticalAlignment != alignment) {
261            this.verticalAlignment = alignment;
262            notifyListeners(new TitleChangeEvent(this));
263        }
264    }
265
266    /**
267     * Returns the flag that indicates whether or not the notification
268     * mechanism is enabled.
269     *
270     * @return The flag.
271     */
272    public boolean getNotify() {
273        return this.notify;
274    }
275
276    /**
277     * Sets the flag that indicates whether or not the notification mechanism
278     * is enabled.  There are certain situations (such as cloning) where you
279     * want to turn notification off temporarily.
280     *
281     * @param flag  the new value of the flag.
282     */
283    public void setNotify(boolean flag) {
284        this.notify = flag;
285        if (flag) {
286            notifyListeners(new TitleChangeEvent(this));
287        }
288    }
289
290    /**
291     * Draws the title on a Java 2D graphics device (such as the screen or a
292     * printer).
293     *
294     * @param g2  the graphics device.
295     * @param area  the area allocated for the title (subclasses should not
296     *              draw outside this area).
297     */
298    @Override
299    public abstract void draw(Graphics2D g2, Rectangle2D area);
300
301    /**
302     * Returns a clone of the title.
303     * <P>
304     * One situation when this is useful is when editing the title properties -
305     * you can edit a clone, and then it is easier to cancel the changes if
306     * necessary.
307     *
308     * @return A clone of the title.
309     *
310     * @throws CloneNotSupportedException not thrown by this class, but it may
311     *         be thrown by subclasses.
312     */
313    @Override
314    public Object clone() throws CloneNotSupportedException {
315        Title duplicate = (Title) super.clone();
316        duplicate.listenerList = new EventListenerList();
317        // RectangleInsets is immutable => same reference in clone OK
318        return duplicate;
319    }
320
321    /**
322     * Registers an object for notification of changes to the title.
323     *
324     * @param listener  the object that is being registered.
325     */
326    public void addChangeListener(TitleChangeListener listener) {
327        this.listenerList.add(TitleChangeListener.class, listener);
328    }
329
330    /**
331     * Unregisters an object for notification of changes to the chart title.
332     *
333     * @param listener  the object that is being unregistered.
334     */
335    public void removeChangeListener(TitleChangeListener listener) {
336        this.listenerList.remove(TitleChangeListener.class, listener);
337    }
338
339    /**
340     * Notifies all registered listeners that the chart title has changed in
341     * some way.
342     *
343     * @param event  an object that contains information about the change to
344     *               the title.
345     */
346    protected void notifyListeners(TitleChangeEvent event) {
347        if (this.notify) {
348            Object[] listeners = this.listenerList.getListenerList();
349            for (int i = listeners.length - 2; i >= 0; i -= 2) {
350                if (listeners[i] == TitleChangeListener.class) {
351                    ((TitleChangeListener) listeners[i + 1]).titleChanged(
352                            event);
353                }
354            }
355        }
356    }
357
358    /**
359     * Tests an object for equality with this title.
360     *
361     * @param obj  the object ({@code null} not permitted).
362     *
363     * @return {@code true} or {@code false}.
364     */
365    @Override
366    public boolean equals(Object obj) {
367        if (obj == this) {
368            return true;
369        }
370        if (!(obj instanceof Title)) {
371            return false;
372        }
373        Title that = (Title) obj;
374        if (this.visible != that.visible) {
375            return false;
376        }
377        if (this.position != that.position) {
378            return false;
379        }
380        if (this.horizontalAlignment != that.horizontalAlignment) {
381            return false;
382        }
383        if (this.verticalAlignment != that.verticalAlignment) {
384            return false;
385        }
386        if (this.notify != that.notify) {
387            return false;
388        }
389        return super.equals(obj);
390    }
391
392    /**
393     * Returns a hashcode for the title.
394     *
395     * @return The hashcode.
396     */
397    @Override
398    public int hashCode() {
399        int result = 193;
400        result = 37 * result + ObjectUtils.hashCode(this.position);
401        result = 37 * result
402                + ObjectUtils.hashCode(this.horizontalAlignment);
403        result = 37 * result + ObjectUtils.hashCode(this.verticalAlignment);
404        return result;
405    }
406
407    /**
408     * Provides serialization support.
409     *
410     * @param stream  the output stream.
411     *
412     * @throws IOException  if there is an I/O error.
413     */
414    private void writeObject(ObjectOutputStream stream) throws IOException {
415        stream.defaultWriteObject();
416    }
417
418    /**
419     * Provides serialization support.
420     *
421     * @param stream  the input stream.
422     *
423     * @throws IOException  if there is an I/O error.
424     * @throws ClassNotFoundException  if there is a classpath problem.
425     */
426    private void readObject(ObjectInputStream stream)
427        throws IOException, ClassNotFoundException {
428        stream.defaultReadObject();
429        this.listenerList = new EventListenerList();
430    }
431
432}