    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, by Object Refinery Limited and Contributors.
    006 *
    007 * Project Info:  http://www.jfree.org/jfreechart/index.html
    008 *
    009 * This library is free software; you can redistribute it and/or modify it
    010 * under the terms of the GNU Lesser General Public License as published by
    011 * the Free Software Foundation; either version 2.1 of the License, or
    012 * (at your option) any later version.
    013 *
    014 * This library is distributed in the hope that it will be useful, but
    015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017 * License for more details.
    018 *
    019 * You should have received a copy of the GNU Lesser General Public
    020 * License along with this library; if not, write to the Free Software
    021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022 * USA.
    023 *
    024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    025 * Other names may be trademarks of their respective owners.]
    026 *
    027 * --------------
    028 * 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
    085package org.jfree.chart.title;
    086
    087import java.awt.Color;
    088import java.awt.Font;
    089import java.awt.Graphics2D;
    090import java.awt.Paint;
    091import java.awt.geom.Rectangle2D;
    092import java.io.IOException;
    093import java.io.ObjectInputStream;
    094import java.io.ObjectOutputStream;
    095import java.io.Serializable;
    096
    097import org.jfree.chart.block.BlockResult;
    098import org.jfree.chart.block.EntityBlockParams;
    099import org.jfree.chart.block.LengthConstraintType;
    100import org.jfree.chart.block.RectangleConstraint;
    101import org.jfree.chart.entity.ChartEntity;
    102import org.jfree.chart.entity.EntityCollection;
    103import org.jfree.chart.entity.StandardEntityCollection;
    104import org.jfree.chart.entity.TitleEntity;
    105import org.jfree.chart.event.TitleChangeEvent;
    106import org.jfree.data.Range;
    107import org.jfree.io.SerialUtilities;
    108import org.jfree.text.G2TextMeasurer;
    109import org.jfree.text.TextBlock;
    110import org.jfree.text.TextBlockAnchor;
    111import org.jfree.text.TextUtilities;
    112import org.jfree.ui.HorizontalAlignment;
    113import org.jfree.ui.RectangleEdge;
    114import org.jfree.ui.RectangleInsets;
    115import org.jfree.ui.Size2D;
    116import org.jfree.ui.VerticalAlignment;
    117import org.jfree.util.ObjectUtilities;
    118import org.jfree.util.PaintUtilities;
    119import org.jfree.util.PublicCloneable;
    120
    121/**
    122 * A chart title that displays a text string with automatic wrapping as
    123 * required.
    124 */
    125public 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