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