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