    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2008, 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-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
    061    package org.jfree.chart.title;
    062
    063    import java.awt.Graphics2D;
    064    import java.awt.geom.Rectangle2D;
    065    import java.io.IOException;
    066    import java.io.ObjectInputStream;
    067    import java.io.ObjectOutputStream;
    068    import java.io.Serializable;
    069
    070    import javax.swing.event.EventListenerList;
    071
    072    import org.jfree.chart.block.AbstractBlock;
    073    import org.jfree.chart.block.Block;
    074    import org.jfree.chart.event.TitleChangeEvent;
    075    import org.jfree.chart.event.TitleChangeListener;
    076    import org.jfree.ui.HorizontalAlignment;
    077    import org.jfree.ui.RectangleEdge;
    078    import org.jfree.ui.RectangleInsets;
    079    import org.jfree.ui.VerticalAlignment;
    080    import 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     */
    089    public 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    }