    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     * TextTitle.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 18-Sep-2001)
    037     * --------------------------
    038     * 18-Sep-2001 : Added standard header (DG);
    039     * 07-Nov-2001 : Separated the JCommon Class Library classes, JFreeChart now
    040     *               requires jcommon.jar (DG);
    041     * 09-Jan-2002 : Updated Javadoc comments (DG);
    042     * 07-Feb-2002 : Changed Insets --> Spacer in AbstractTitle.java (DG);
    043     * 06-Mar-2002 : Updated import statements (DG);
    044     * 25-Jun-2002 : Removed redundant imports (DG);
    045     * 18-Sep-2002 : Fixed errors reported by Checkstyle (DG);
    046     * 28-Oct-2002 : Small modifications while changing JFreeChart class (DG);
    047     * 13-Mar-2003 : Changed width used for relative spacing to fix bug 703050 (DG);
    048     * 26-Mar-2003 : Implemented Serializable (DG);
    049     * 15-Jul-2003 : Fixed null pointer exception (DG);
    050     * 11-Sep-2003 : Implemented Cloneable (NB)
    051     * 22-Sep-2003 : Added checks for null values and throw nullpointer
    052     *               exceptions (TM);
    053     *               Background paint was not serialized.
    054     * 07-Oct-2003 : Added fix for exception caused by empty string in title (DG);
    055     * 29-Oct-2003 : Added workaround for text alignment in PDF output (DG);
    056     * 03-Feb-2004 : Fixed bug in getPreferredWidth() method (DG);
    057     * 17-Feb-2004 : Added clone() method and fixed bug in equals() method (DG);
    058     * 01-Apr-2004 : Changed java.awt.geom.Dimension2D to org.jfree.ui.Size2D
    059     *               because of JDK bug 4976448 which persists on JDK 1.3.1.  Also
    060     *               fixed bug in getPreferredHeight() method (DG);
    061     * 29-Apr-2004 : Fixed bug in getPreferredWidth() method - see bug id
    062     *               944173 (DG);
    063     * 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0
    064     *               release (DG);
    065     * 08-Feb-2005 : Updated for changes in RectangleConstraint class (DG);
    066     * 11-Feb-2005 : Implemented PublicCloneable (DG);
    067     * 20-Apr-2005 : Added support for tooltips (DG);
    068     * 26-Apr-2005 : Removed LOGGER (DG);
    069     * 06-Jun-2005 : Modified equals() to handle GradientPaint (DG);
    070     * 06-Jul-2005 : Added flag to control whether or not the title expands to
    071     *               fit the available space (DG);
    072     * 07-Oct-2005 : Added textAlignment attribute (DG);
    073     * ------------- JFREECHART 1.0.x RELEASED ------------------------------------
    074     * 13-Dec-2005 : Fixed bug 1379331 - incorrect drawing with LEFT or RIGHT
    075     *               title placement (DG);
    076     * 19-Dec-2007 : Implemented some of the missing arrangement options (DG);
    077     * 28-Apr-2008 : Added option for maximum lines, and fixed minor bugs in
    078     *               equals() method (DG);
    079     *
    080     */
    081
    082    package org.jfree.chart.title;
    083
    084    import java.awt.Color;
    085    import java.awt.Font;
    086    import java.awt.Graphics2D;
    087    import java.awt.Paint;
    088    import java.awt.geom.Rectangle2D;
    089    import java.io.IOException;
    090    import java.io.ObjectInputStream;
    091    import java.io.ObjectOutputStream;
    092    import java.io.Serializable;
    093
    094    import org.jfree.chart.block.BlockResult;
    095    import org.jfree.chart.block.EntityBlockParams;
    096    import org.jfree.chart.block.LengthConstraintType;
    097    import org.jfree.chart.block.RectangleConstraint;
    098    import org.jfree.chart.entity.ChartEntity;
    099    import org.jfree.chart.entity.EntityCollection;
    100    import org.jfree.chart.entity.StandardEntityCollection;
    101    import org.jfree.chart.event.TitleChangeEvent;
    102    import org.jfree.data.Range;
    103    import org.jfree.io.SerialUtilities;
    104    import org.jfree.text.G2TextMeasurer;
    105    import org.jfree.text.TextBlock;
    106    import org.jfree.text.TextBlockAnchor;
    107    import org.jfree.text.TextUtilities;
    108    import org.jfree.ui.HorizontalAlignment;
    109    import org.jfree.ui.RectangleEdge;
    110    import org.jfree.ui.RectangleInsets;
    111    import org.jfree.ui.Size2D;
    112    import org.jfree.ui.VerticalAlignment;
    113    import org.jfree.util.ObjectUtilities;
    114    import org.jfree.util.PaintUtilities;
    115    import org.jfree.util.PublicCloneable;
    116
    117    /**
    118     * A chart title that displays a text string with automatic wrapping as
    119     * required.
    120     */
    121    public class TextTitle extends Title
    122                           implements Serializable, Cloneable, PublicCloneable {
    123
    124        /** For serialization. */
    125        private static final long serialVersionUID = 8372008692127477443L;
    126
    127        /** The default font. */
    128        public static final Font DEFAULT_FONT = new Font("SansSerif", Font.BOLD,
    129                12);
    130
    131        /** The default text color. */
    132        public static final Paint DEFAULT_TEXT_PAINT = Color.black;
    133
    134        /** The title text. */
    135        private String text;
    136
    137        /** The font used to display the title. */
    138        private Font font;
    139
    140        /** The text alignment. */
    141        private HorizontalAlignment textAlignment;
    142
    143        /** The paint used to display the title text. */
    144        private transient Paint paint;
    145
    146        /** The background paint. */
    147        private transient Paint backgroundPaint;
    148
    149        /** The tool tip text (can be <code>null</code>). */
    150        private String toolTipText;
    151
    152        /** The URL text (can be <code>null</code>). */
    153        private String urlText;
    154
    155        /** The content. */
    156        private TextBlock content;
    157
    158        /**
    159         * A flag that controls whether the title expands to fit the available
    160         * space..
    161         */
    162        private boolean expandToFitSpace = false;
    163
    164        /**
    165         * The maximum number of lines to display.
    166         *
    167         * @since 1.0.10
    168         */
    169        private int maximumLinesToDisplay = Integer.MAX_VALUE;
    170
    171        /**
    172         * Creates a new title, using default attributes where necessary.
    173         */
    174        public TextTitle() {
    175            this("");
    176        }
    177
    178        /**
    179         * Creates a new title, using default attributes where necessary.
    180         *
    181         * @param text  the title text (<code>null</code> not permitted).
    182         */
    183        public TextTitle(String text) {
    184            this(text, TextTitle.DEFAULT_FONT, TextTitle.DEFAULT_TEXT_PAINT,
    185                    Title.DEFAULT_POSITION, Title.DEFAULT_HORIZONTAL_ALIGNMENT,
    186                    Title.DEFAULT_VERTICAL_ALIGNMENT, Title.DEFAULT_PADDING);
    187        }
    188
    189        /**
    190         * Creates a new title, using default attributes where necessary.
    191         *
    192         * @param text  the title text (<code>null</code> not permitted).
    193         * @param font  the title font (<code>null</code> not permitted).
    194         */
    195        public TextTitle(String text, Font font) {
    196            this(text, font, TextTitle.DEFAULT_TEXT_PAINT, Title.DEFAULT_POSITION,
    197                    Title.DEFAULT_HORIZONTAL_ALIGNMENT,
    198                    Title.DEFAULT_VERTICAL_ALIGNMENT, Title.DEFAULT_PADDING);
    199        }
    200
    201        /**
    202         * Creates a new title.
    203         *
    204         * @param text  the text for the title (<code>null</code> not permitted).
    205         * @param font  the title font (<code>null</code> not permitted).
    206         * @param paint  the title paint (<code>null</code> not permitted).
    207         * @param position  the title position (<code>null</code> not permitted).
    208         * @param horizontalAlignment  the horizontal alignment (<code>null</code>
    209         *                             not permitted).
    210         * @param verticalAlignment  the vertical alignment (<code>null</code> not
    211         *                           permitted).
    212         * @param padding  the space to leave around the outside of the title.
    213         */
    214        public TextTitle(String text, Font font, Paint paint,
    215                         RectangleEdge position,
    216                         HorizontalAlignment horizontalAlignment,
    217                         VerticalAlignment verticalAlignment,
    218                         RectangleInsets padding) {
    219
    220            super(position, horizontalAlignment, verticalAlignment, padding);
    221
    222            if (text == null) {
    223                throw new NullPointerException("Null 'text' argument.");
    224            }
    225            if (font == null) {
    226                throw new NullPointerException("Null 'font' argument.");
    227            }
    228            if (paint == null) {
    229                throw new NullPointerException("Null 'paint' argument.");
    230            }
    231            this.text = text;
    232            this.font = font;
    233            this.paint = paint;
    234            // the textAlignment and the horizontalAlignment are separate things,
    235            // but it makes sense for the default textAlignment to match the
    236            // title's horizontal alignment...
    237            this.textAlignment = horizontalAlignment;
    238            this.backgroundPaint = null;
    239            this.content = null;
    240            this.toolTipText = null;
    241            this.urlText = null;
    242
    243        }
    244
    245        /**
    246         * Returns the title text.
    247         *
    248         * @return The text (never <code>null</code>).
    249         *
    250         * @see #setText(String)
    251         */
    252        public String getText() {
    253            return this.text;
    254        }
    255
    256        /**
    257         * Sets the title to the specified text and sends a
    258         * {@link TitleChangeEvent} to all registered listeners.
    259         *
    260         * @param text  the text (<code>null</code> not permitted).
    261         */
    262        public void setText(String text) {
    263            if (text == null) {
    264                throw new IllegalArgumentException("Null 'text' argument.");
    265            }
    266            if (!this.text.equals(text)) {
    267                this.text = text;
    268                notifyListeners(new TitleChangeEvent(this));
    269            }
    270        }
    271
    272        /**
    273         * Returns the text alignment.  This controls how the text is aligned
    274         * within the title's bounds, whereas the title's horizontal alignment
    275         * controls how the title's bounding rectangle is aligned within the
    276         * drawing space.
    277         *
    278         * @return The text alignment.
    279         */
    280        public HorizontalAlignment getTextAlignment() {
    281            return this.textAlignment;
    282        }
    283
    284        /**
    285         * Sets the text alignment and sends a {@link TitleChangeEvent} to
    286         * all registered listeners.
    287         *
    288         * @param alignment  the alignment (<code>null</code> not permitted).
    289         */
    290        public void setTextAlignment(HorizontalAlignment alignment) {
    291            if (alignment == null) {
    292                throw new IllegalArgumentException("Null 'alignment' argument.");
    293            }
    294            this.textAlignment = alignment;
    295            notifyListeners(new TitleChangeEvent(this));
    296        }
    297
    298        /**
    299         * Returns the font used to display the title string.
    300         *
    301         * @return The font (never <code>null</code>).
    302         *
    303         * @see #setFont(Font)
    304         */
    305        public Font getFont() {
    306            return this.font;
    307        }
    308
    309        /**
    310         * Sets the font used to display the title string.  Registered listeners
    311         * are notified that the title has been modified.
    312         *
    313         * @param font  the new font (<code>null</code> not permitted).
    314         *
    315         * @see #getFont()
    316         */
    317        public void setFont(Font font) {
    318            if (font == null) {
    319                throw new IllegalArgumentException("Null 'font' argument.");
    320            }
    321            if (!this.font.equals(font)) {
    322                this.font = font;
    323                notifyListeners(new TitleChangeEvent(this));
    324            }
    325        }
    326
    327        /**
    328         * Returns the paint used to display the title string.
    329         *
    330         * @return The paint (never <code>null</code>).
    331         *
    332         * @see #setPaint(Paint)
    333         */
    334        public Paint getPaint() {
    335            return this.paint;
    336        }
    337
    338        /**
    339         * Sets the paint used to display the title string.  Registered listeners
    340         * are notified that the title has been modified.
    341         *
    342         * @param paint  the new paint (<code>null</code> not permitted).
    343         *
    344         * @see #getPaint()
    345         */
    346        public void setPaint(Paint paint) {
    347            if (paint == null) {
    348                throw new IllegalArgumentException("Null 'paint' argument.");
    349            }
    350            if (!this.paint.equals(paint)) {
    351                this.paint = paint;
    352                notifyListeners(new TitleChangeEvent(this));
    353            }
    354        }
    355
    356        /**
    357         * Returns the background paint.
    358         *
    359         * @return The paint (possibly <code>null</code>).
    360         */
    361        public Paint getBackgroundPaint() {
    362            return this.backgroundPaint;
    363        }
    364
    365        /**
    366         * Sets the background paint and sends a {@link TitleChangeEvent} to all
    367         * registered listeners.  If you set this attribute to <code>null</code>,
    368         * no background is painted (which makes the title background transparent).
    369         *
    370         * @param paint  the background paint (<code>null</code> permitted).
    371         */
    372        public void setBackgroundPaint(Paint paint) {
    373            this.backgroundPaint = paint;
    374            notifyListeners(new TitleChangeEvent(this));
    375        }
    376
    377        /**
    378         * Returns the tool tip text.
    379         *
    380         * @return The tool tip text (possibly <code>null</code>).
    381         */
    382        public String getToolTipText() {
    383            return this.toolTipText;
    384        }
    385
    386        /**
    387         * Sets the tool tip text to the specified text and sends a
    388         * {@link TitleChangeEvent} to all registered listeners.
    389         *
    390         * @param text  the text (<code>null</code> permitted).
    391         */
    392        public void setToolTipText(String text) {
    393            this.toolTipText = text;
    394            notifyListeners(new TitleChangeEvent(this));
    395        }
    396
    397        /**
    398         * Returns the URL text.
    399         *
    400         * @return The URL text (possibly <code>null</code>).
    401         */
    402        public String getURLText() {
    403            return this.urlText;
    404        }
    405
    406        /**
    407         * Sets the URL text to the specified text and sends a
    408         * {@link TitleChangeEvent} to all registered listeners.
    409         *
    410         * @param text  the text (<code>null</code> permitted).
    411         */
    412        public void setURLText(String text) {
    413            this.urlText = text;
    414            notifyListeners(new TitleChangeEvent(this));
    415        }
    416
    417        /**
    418         * Returns the flag that controls whether or not the title expands to fit
    419         * the available space.
    420         *
    421         * @return The flag.
    422         */
    423        public boolean getExpandToFitSpace() {
    424            return this.expandToFitSpace;
    425        }
    426
    427        /**
    428         * Sets the flag that controls whether the title expands to fit the
    429         * available space, and sends a {@link TitleChangeEvent} to all registered
    430         * listeners.
    431         *
    432         * @param expand  the flag.
    433         */
    434        public void setExpandToFitSpace(boolean expand) {
    435            this.expandToFitSpace = expand;
    436            notifyListeners(new TitleChangeEvent(this));
    437        }
    438
    439        /**
    440         * Returns the maximum number of lines to display.
    441         *
    442         * @return The maximum.
    443         *
    444         * @since 1.0.10
    445         *
    446         * @see #setMaximumLinesToDisplay(int)
    447         */
    448        public int getMaximumLinesToDisplay() {
    449            return this.maximumLinesToDisplay;
    450        }
    451
    452        /**
    453         * Sets the maximum number of lines to display and sends a
    454         * {@link TitleChangeEvent} to all registered listeners.
    455         *
    456         * @param max  the maximum.
    457         *
    458         * @since 1.0.10.
    459         *
    460         * @see #getMaximumLinesToDisplay()
    461         */
    462        public void setMaximumLinesToDisplay(int max) {
    463            this.maximumLinesToDisplay = max;
    464            notifyListeners(new TitleChangeEvent(this));
    465        }
    466
    467        /**
    468         * Arranges the contents of the block, within the given constraints, and
    469         * returns the block size.
    470         *
    471         * @param g2  the graphics device.
    472         * @param constraint  the constraint (<code>null</code> not permitted).
    473         *
    474         * @return The block size (in Java2D units, never <code>null</code>).
    475         */
    476        public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) {
    477            RectangleConstraint cc = toContentConstraint(constraint);
    478            LengthConstraintType w = cc.getWidthConstraintType();
    479            LengthConstraintType h = cc.getHeightConstraintType();
    480            Size2D contentSize = null;
    481            if (w == LengthConstraintType.NONE) {
    482                if (h == LengthConstraintType.NONE) {
    483                    contentSize = arrangeNN(g2);
    484                }
    485                else if (h == LengthConstraintType.RANGE) {
    486                    throw new RuntimeException("Not yet implemented.");
    487                }
    488                else if (h == LengthConstraintType.FIXED) {
    489                    throw new RuntimeException("Not yet implemented.");
    490                }
    491            }
    492            else if (w == LengthConstraintType.RANGE) {
    493                if (h == LengthConstraintType.NONE) {
    494                    contentSize = arrangeRN(g2, cc.getWidthRange());
    495                }
    496                else if (h == LengthConstraintType.RANGE) {
    497                    contentSize = arrangeRR(g2, cc.getWidthRange(),
    498                            cc.getHeightRange());
    499                }
    500                else if (h == LengthConstraintType.FIXED) {
    501                    throw new RuntimeException("Not yet implemented.");
    502                }
    503            }
    504            else if (w == LengthConstraintType.FIXED) {
    505                if (h == LengthConstraintType.NONE) {
    506                    contentSize = arrangeFN(g2, cc.getWidth());
    507                }
    508                else if (h == LengthConstraintType.RANGE) {
    509                    throw new RuntimeException("Not yet implemented.");
    510                }
    511                else if (h == LengthConstraintType.FIXED) {
    512                    throw new RuntimeException("Not yet implemented.");
    513                }
    514            }
    515            return new Size2D(calculateTotalWidth(contentSize.getWidth()),
    516                    calculateTotalHeight(contentSize.getHeight()));
    517        }
    518
    519        /**
    520         * Arranges the content for this title assuming no bounds on the width
    521         * or the height, and returns the required size.  This will reflect the
    522         * fact that a text title positioned on the left or right of a chart will
    523         * be rotated by 90 degrees.
    524         *
    525         * @param g2  the graphics target.
    526         *
    527         * @return The content size.
    528         *
    529         * @since 1.0.9
    530         */
    531        protected Size2D arrangeNN(Graphics2D g2) {
    532            Range max = new Range(0.0, Float.MAX_VALUE);
    533            return arrangeRR(g2, max, max);
    534        }
    535
    536        /**
    537         * Arranges the content for this title assuming a fixed width and no bounds
    538         * on the height, and returns the required size.  This will reflect the
    539         * fact that a text title positioned on the left or right of a chart will
    540         * be rotated by 90 degrees.
    541         *
    542         * @param g2  the graphics target.
    543         * @param w  the width.
    544         *
    545         * @return The content size.
    546         *
    547         * @since 1.0.9
    548         */
    549        protected Size2D arrangeFN(Graphics2D g2, double w) {
    550            RectangleEdge position = getPosition();
    551            if (position == RectangleEdge.TOP || position == RectangleEdge.BOTTOM) {
    552                float maxWidth = (float) w;
    553                g2.setFont(this.font);
    554                this.content = TextUtilities.createTextBlock(this.text, this.font,
    555                        this.paint, maxWidth, this.maximumLinesToDisplay,
    556                        new G2TextMeasurer(g2));
    557                this.content.setLineAlignment(this.textAlignment);
    558                Size2D contentSize = this.content.calculateDimensions(g2);
    559                if (this.expandToFitSpace) {
    560                    return new Size2D(maxWidth, contentSize.getHeight());
    561                }
    562                else {
    563                    return contentSize;
    564                }
    565            }
    566            else if (position == RectangleEdge.LEFT || position
    567                    == RectangleEdge.RIGHT) {
    568                float maxWidth = Float.MAX_VALUE;
    569                g2.setFont(this.font);
    570                this.content = TextUtilities.createTextBlock(this.text, this.font,
    571                        this.paint, maxWidth, this.maximumLinesToDisplay,
    572                        new G2TextMeasurer(g2));
    573                this.content.setLineAlignment(this.textAlignment);
    574                Size2D contentSize = this.content.calculateDimensions(g2);
    575
    576                // transpose the dimensions, because the title is rotated
    577                if (this.expandToFitSpace) {
    578                    return new Size2D(contentSize.getHeight(), maxWidth);
    579                }
    580                else {
    581                    return new Size2D(contentSize.height, contentSize.width);
    582                }
    583            }
    584            else {
    585                throw new RuntimeException("Unrecognised exception.");
    586            }
    587        }
    588
    589        /**
    590         * Arranges the content for this title assuming a range constraint for the
    591         * width and no bounds on the height, and returns the required size.  This
    592         * will reflect the fact that a text title positioned on the left or right
    593         * of a chart will be rotated by 90 degrees.
    594         *
    595         * @param g2  the graphics target.
    596         * @param widthRange  the range for the width.
    597         *
    598         * @return The content size.
    599         *
    600         * @since 1.0.9
    601         */
    602        protected Size2D arrangeRN(Graphics2D g2, Range widthRange) {
    603            Size2D s = arrangeNN(g2);
    604            if (widthRange.contains(s.getWidth())) {
    605                return s;
    606            }
    607            double ww = widthRange.constrain(s.getWidth());
    608            return arrangeFN(g2, ww);
    609        }
    610
    611        /**
    612         * Returns the content size for the title.  This will reflect the fact that
    613         * a text title positioned on the left or right of a chart will be rotated
    614         * 90 degrees.
    615         *
    616         * @param g2  the graphics device.
    617         * @param widthRange  the width range.
    618         * @param heightRange  the height range.
    619         *
    620         * @return The content size.
    621         */
    622        protected Size2D arrangeRR(Graphics2D g2, Range widthRange,
    623                Range heightRange) {
    624            RectangleEdge position = getPosition();
    625            if (position == RectangleEdge.TOP || position == RectangleEdge.BOTTOM) {
    626                float maxWidth = (float) widthRange.getUpperBound();
    627                g2.setFont(this.font);
    628                this.content = TextUtilities.createTextBlock(this.text, this.font,
    629                        this.paint, maxWidth, this.maximumLinesToDisplay,
    630                        new G2TextMeasurer(g2));
    631                this.content.setLineAlignment(this.textAlignment);
    632                Size2D contentSize = this.content.calculateDimensions(g2);
    633                if (this.expandToFitSpace) {
    634                    return new Size2D(maxWidth, contentSize.getHeight());
    635                }
    636                else {
    637                    return contentSize;
    638                }
    639            }
    640            else if (position == RectangleEdge.LEFT || position
    641                    == RectangleEdge.RIGHT) {
    642                float maxWidth = (float) heightRange.getUpperBound();
    643                g2.setFont(this.font);
    644                this.content = TextUtilities.createTextBlock(this.text, this.font,
    645                        this.paint, maxWidth, this.maximumLinesToDisplay,
    646                        new G2TextMeasurer(g2));
    647                this.content.setLineAlignment(this.textAlignment);
    648                Size2D contentSize = this.content.calculateDimensions(g2);
    649
    650                // transpose the dimensions, because the title is rotated
    651                if (this.expandToFitSpace) {
    652                    return new Size2D(contentSize.getHeight(), maxWidth);
    653                }
    654                else {
    655                    return new Size2D(contentSize.height, contentSize.width);
    656                }
    657            }
    658            else {
    659                throw new RuntimeException("Unrecognised exception.");
    660            }
    661        }
    662
    663        /**
    664         * Draws the title on a Java 2D graphics device (such as the screen or a
    665         * printer).
    666         *
    667         * @param g2  the graphics device.
    668         * @param area  the area allocated for the title.
    669         */
    670        public void draw(Graphics2D g2, Rectangle2D area) {
    671            draw(g2, area, null);
    672        }
    673
    674        /**
    675         * Draws the block within the specified area.
    676         *
    677         * @param g2  the graphics device.
    678         * @param area  the area.
    679         * @param params  if this is an instance of {@link EntityBlockParams} it
    680         *                is used to determine whether or not an
    681         *                {@link EntityCollection} is returned by this method.
    682         *
    683         * @return An {@link EntityCollection} containing a chart entity for the
    684         *         title, or <code>null</code>.
    685         */
    686        public Object draw(Graphics2D g2, Rectangle2D area, Object params) {
    687            if (this.content == null) {
    688                return null;
    689            }
    690            area = trimMargin(area);
    691            drawBorder(g2, area);
    692            if (this.text.equals("")) {
    693                return null;
    694            }
    695            ChartEntity entity = null;
    696            if (params instanceof EntityBlockParams) {
    697                EntityBlockParams p = (EntityBlockParams) params;
    698                if (p.getGenerateEntities()) {
    699                    entity = new ChartEntity(area, this.toolTipText, this.urlText);
    700                }
    701            }
    702            area = trimBorder(area);
    703            if (this.backgroundPaint != null) {
    704                g2.setPaint(this.backgroundPaint);
    705                g2.fill(area);
    706            }
    707            area = trimPadding(area);
    708            RectangleEdge position = getPosition();
    709            if (position == RectangleEdge.TOP || position == RectangleEdge.BOTTOM) {
    710                drawHorizontal(g2, area);
    711            }
    712            else if (position == RectangleEdge.LEFT
    713                     || position == RectangleEdge.RIGHT) {
    714                drawVertical(g2, area);
    715            }
    716            BlockResult result = new BlockResult();
    717            if (entity != null) {
    718                StandardEntityCollection sec = new StandardEntityCollection();
    719                sec.add(entity);
    720                result.setEntityCollection(sec);
    721            }
    722            return result;
    723        }
    724
    725        /**
    726         * Draws a the title horizontally within the specified area.  This method
    727         * will be called from the {@link #draw(Graphics2D, Rectangle2D) draw}
    728         * method.
    729         *
    730         * @param g2  the graphics device.
    731         * @param area  the area for the title.
    732         */
    733        protected void drawHorizontal(Graphics2D g2, Rectangle2D area) {
    734            Rectangle2D titleArea = (Rectangle2D) area.clone();
    735            g2.setFont(this.font);
    736            g2.setPaint(this.paint);
    737            TextBlockAnchor anchor = null;
    738            float x = 0.0f;
    739            HorizontalAlignment horizontalAlignment = getHorizontalAlignment();
    740            if (horizontalAlignment == HorizontalAlignment.LEFT) {
    741                x = (float) titleArea.getX();
    742                anchor = TextBlockAnchor.TOP_LEFT;
    743            }
    744            else if (horizontalAlignment == HorizontalAlignment.RIGHT) {
    745                x = (float) titleArea.getMaxX();
    746                anchor = TextBlockAnchor.TOP_RIGHT;
    747            }
    748            else if (horizontalAlignment == HorizontalAlignment.CENTER) {
    749                x = (float) titleArea.getCenterX();
    750                anchor = TextBlockAnchor.TOP_CENTER;
    751            }
    752            float y = 0.0f;
    753            RectangleEdge position = getPosition();
    754            if (position == RectangleEdge.TOP) {
    755                y = (float) titleArea.getY();
    756            }
    757            else if (position == RectangleEdge.BOTTOM) {
    758                y = (float) titleArea.getMaxY();
    759                if (horizontalAlignment == HorizontalAlignment.LEFT) {
    760                    anchor = TextBlockAnchor.BOTTOM_LEFT;
    761                }
    762                else if (horizontalAlignment == HorizontalAlignment.CENTER) {
    763                    anchor = TextBlockAnchor.BOTTOM_CENTER;
    764                }
    765                else if (horizontalAlignment == HorizontalAlignment.RIGHT) {
    766                    anchor = TextBlockAnchor.BOTTOM_RIGHT;
    767                }
    768            }
    769            this.content.draw(g2, x, y, anchor);
    770        }
    771
    772        /**
    773         * Draws a the title vertically within the specified area.  This method
    774         * will be called from the {@link #draw(Graphics2D, Rectangle2D) draw}
    775         * method.
    776         *
    777         * @param g2  the graphics device.
    778         * @param area  the area for the title.
    779         */
    780        protected void drawVertical(Graphics2D g2, Rectangle2D area) {
    781            Rectangle2D titleArea = (Rectangle2D) area.clone();
    782            g2.setFont(this.font);
    783            g2.setPaint(this.paint);
    784            TextBlockAnchor anchor = null;
    785            float y = 0.0f;
    786            VerticalAlignment verticalAlignment = getVerticalAlignment();
    787            if (verticalAlignment == VerticalAlignment.TOP) {
    788                y = (float) titleArea.getY();
    789                anchor = TextBlockAnchor.TOP_RIGHT;
    790            }
    791            else if (verticalAlignment == VerticalAlignment.BOTTOM) {
    792                y = (float) titleArea.getMaxY();
    793                anchor = TextBlockAnchor.TOP_LEFT;
    794            }
    795            else if (verticalAlignment == VerticalAlignment.CENTER) {
    796                y = (float) titleArea.getCenterY();
    797                anchor = TextBlockAnchor.TOP_CENTER;
    798            }
    799            float x = 0.0f;
    800            RectangleEdge position = getPosition();
    801            if (position == RectangleEdge.LEFT) {
    802                x = (float) titleArea.getX();
    803            }
    804            else if (position == RectangleEdge.RIGHT) {
    805                x = (float) titleArea.getMaxX();
    806                if (verticalAlignment == VerticalAlignment.TOP) {
    807                    anchor = TextBlockAnchor.BOTTOM_RIGHT;
    808                }
    809                else if (verticalAlignment == VerticalAlignment.CENTER) {
    810                    anchor = TextBlockAnchor.BOTTOM_CENTER;
    811                }
    812                else if (verticalAlignment == VerticalAlignment.BOTTOM) {
    813                    anchor = TextBlockAnchor.BOTTOM_LEFT;
    814                }
    815            }
    816            this.content.draw(g2, x, y, anchor, x, y, -Math.PI / 2.0);
    817        }
    818
    819        /**
    820         * Tests this title for equality with another object.
    821         *
    822         * @param obj  the object (<code>null</code> permitted).
    823         *
    824         * @return <code>true</code> or <code>false</code>.
    825         */
    826        public boolean equals(Object obj) {
    827            if (obj == this) {
    828                return true;
    829            }
    830            if (!(obj instanceof TextTitle)) {
    831                return false;
    832            }
    833            TextTitle that = (TextTitle) obj;
    834            if (!ObjectUtilities.equal(this.text, that.text)) {
    835                return false;
    836            }
    837            if (!ObjectUtilities.equal(this.font, that.font)) {
    838                return false;
    839            }
    840            if (!PaintUtilities.equal(this.paint, that.paint)) {
    841                return false;
    842            }
    843            if (this.textAlignment != that.textAlignment) {
    844                return false;
    845            }
    846            if (!PaintUtilities.equal(this.backgroundPaint, that.backgroundPaint)) {
    847                return false;
    848            }
    849            if (this.maximumLinesToDisplay != that.maximumLinesToDisplay) {
    850                return false;
    851            }
    852            if (this.expandToFitSpace != that.expandToFitSpace) {
    853                return false;
    854            }
    855            if (!ObjectUtilities.equal(this.toolTipText, that.toolTipText)) {
    856                return false;
    857            }
    858            if (!ObjectUtilities.equal(this.urlText, that.urlText)) {
    859                return false;
    860            }
    861            return super.equals(obj);
    862        }
    863
    864        /**
    865         * Returns a hash code.
    866         *
    867         * @return A hash code.
    868         */
    869        public int hashCode() {
    870            int result = super.hashCode();
    871            result = 29 * result + (this.text != null ? this.text.hashCode() : 0);
    872            result = 29 * result + (this.font != null ? this.font.hashCode() : 0);
    873            result = 29 * result + (this.paint != null ? this.paint.hashCode() : 0);
    874            result = 29 * result + (this.backgroundPaint != null
    875                    ? this.backgroundPaint.hashCode() : 0);
    876            return result;
    877        }
    878
    879        /**
    880         * Returns a clone of this object.
    881         *
    882         * @return A clone.
    883         *
    884         * @throws CloneNotSupportedException never.
    885         */
    886        public Object clone() throws CloneNotSupportedException {
    887            return super.clone();
    888        }
    889
    890        /**
    891         * Provides serialization support.
    892         *
    893         * @param stream  the output stream.
    894         *
    895         * @throws IOException  if there is an I/O error.
    896         */
    897        private void writeObject(ObjectOutputStream stream) throws IOException {
    898            stream.defaultWriteObject();
    899            SerialUtilities.writePaint(this.paint, stream);
    900            SerialUtilities.writePaint(this.backgroundPaint, stream);
    901        }
    902
    903        /**
    904         * Provides serialization support.
    905         *
    906         * @param stream  the input stream.
    907         *
    908         * @throws IOException  if there is an I/O error.
    909         * @throws ClassNotFoundException  if there is a classpath problem.
    910         */
    911        private void readObject(ObjectInputStream stream)
    912                throws IOException, ClassNotFoundException {
    913            stream.defaultReadObject();
    914            this.paint = SerialUtilities.readPaint(stream);
    915            this.backgroundPaint = SerialUtilities.readPaint(stream);
    916        }
    917
    918    }
    919