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