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