    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
    006     *
    007     * Project Info:  http://www.jfree.org/jfreechart/index.html
    008     *
    009     * This library is free software; you can redistribute it and/or modify it
    010     * under the terms of the GNU Lesser General Public License as published by
    011     * the Free Software Foundation; either version 2.1 of the License, or
    012     * (at your option) any later version.
    013     *
    014     * This library is distributed in the hope that it will be useful, but
    015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017     * License for more details.
    018     *
    019     * You should have received a copy of the GNU Lesser General Public
    020     * License along with this library; if not, write to the Free Software
    021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022     * USA.
    023     *
    024     * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
    025     * in the United States and other countries.]
    026     *
    027     * --------------
    028     * TextTitle.java
    029     * --------------
    030     * (C) Copyright 2000-2007, by David Berry and Contributors.
    031     *
    032     * Original Author:  David Berry;
    033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034     *                   Nicolas Brodu;
    035     *
    036     * Changes (from 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     *
    078     */
    079
    080    package org.jfree.chart.title;
    081
    082    import java.awt.Color;
    083    import java.awt.Font;
    084    import java.awt.Graphics2D;
    085    import java.awt.Paint;
    086    import java.awt.geom.Rectangle2D;
    087    import java.io.IOException;
    088    import java.io.ObjectInputStream;
    089    import java.io.ObjectOutputStream;
    090    import java.io.Serializable;
    091
    092    import org.jfree.chart.block.BlockResult;
    093    import org.jfree.chart.block.EntityBlockParams;
    094    import org.jfree.chart.block.LengthConstraintType;
    095    import org.jfree.chart.block.RectangleConstraint;
    096    import org.jfree.chart.entity.ChartEntity;
    097    import org.jfree.chart.entity.EntityCollection;
    098    import org.jfree.chart.entity.StandardEntityCollection;
    099    import org.jfree.chart.event.TitleChangeEvent;
    100    import org.jfree.data.Range;
    101    import org.jfree.io.SerialUtilities;
    102    import org.jfree.text.G2TextMeasurer;
    103    import org.jfree.text.TextBlock;
    104    import org.jfree.text.TextBlockAnchor;
    105    import org.jfree.text.TextUtilities;
    106    import org.jfree.ui.HorizontalAlignment;
    107    import org.jfree.ui.RectangleEdge;
    108    import org.jfree.ui.RectangleInsets;
    109    import org.jfree.ui.Size2D;
    110    import org.jfree.ui.VerticalAlignment;
    111    import org.jfree.util.ObjectUtilities;
    112    import org.jfree.util.PaintUtilities;
    113    import org.jfree.util.PublicCloneable;
    114
    115    /**
    116     * A chart title that displays a text string with automatic wrapping as
    117     * required.
    118     */
    119    public class TextTitle extends Title
    120                           implements Serializable, Cloneable, PublicCloneable {
    121
    122        /** For serialization. */
    123        private static final long serialVersionUID = 8372008692127477443L;
    124
    125        /** The default font. */
    126        public static final Font DEFAULT_FONT = new Font("SansSerif", Font.BOLD,
    127                12);
    128
    129        /** The default text color. */
    130        public static final Paint DEFAULT_TEXT_PAINT = Color.black;
    131
    132        /** The title text. */
    133        private String text;
    134
    135        /** The font used to display the title. */
    136        private Font font;
    137
    138        /** The text alignment. */
    139        private HorizontalAlignment textAlignment;
    140
    141        /** The paint used to display the title text. */
    142        private transient Paint paint;
    143
    144        /** The background paint. */
    145        private transient Paint backgroundPaint;
    146
    147        /** The tool tip text (can be <code>null</code>). */
    148        private String toolTipText;
    149
    150        /** The URL text (can be <code>null</code>). */
    151        private String urlText;
    152
    153        /** The content. */
    154        private TextBlock content;
    155
    156        /**
    157         * A flag that controls whether the title expands to fit the available
    158         * space..
    159         */
    160        private boolean expandToFitSpace = false;
    161
    162        /**
    163         * Creates a new title, using default attributes where necessary.
    164         */
    165        public TextTitle() {
    166            this("");
    167        }
    168
    169        /**
    170         * Creates a new title, using default attributes where necessary.
    171         *
    172         * @param text  the title text (<code>null</code> not permitted).
    173         */
    174        public TextTitle(String text) {
    175            this(text, TextTitle.DEFAULT_FONT, TextTitle.DEFAULT_TEXT_PAINT,
    176                    Title.DEFAULT_POSITION, Title.DEFAULT_HORIZONTAL_ALIGNMENT,
    177                    Title.DEFAULT_VERTICAL_ALIGNMENT, Title.DEFAULT_PADDING);
    178        }
    179
    180        /**
    181         * Creates a new title, using default attributes where necessary.
    182         *
    183         * @param text  the title text (<code>null</code> not permitted).
    184         * @param font  the title font (<code>null</code> not permitted).
    185         */
    186        public TextTitle(String text, Font font) {
    187            this(text, font, TextTitle.DEFAULT_TEXT_PAINT, Title.DEFAULT_POSITION,
    188                    Title.DEFAULT_HORIZONTAL_ALIGNMENT,
    189                    Title.DEFAULT_VERTICAL_ALIGNMENT, Title.DEFAULT_PADDING);
    190        }
    191
    192        /**
    193         * Creates a new title.
    194         *
    195         * @param text  the text for the title (<code>null</code> not permitted).
    196         * @param font  the title font (<code>null</code> not permitted).
    197         * @param paint  the title paint (<code>null</code> not permitted).
    198         * @param position  the title position (<code>null</code> not permitted).
    199         * @param horizontalAlignment  the horizontal alignment (<code>null</code>
    200         *                             not permitted).
    201         * @param verticalAlignment  the vertical alignment (<code>null</code> not
    202         *                           permitted).
    203         * @param padding  the space to leave around the outside of the title.
    204         */
    205        public TextTitle(String text, Font font, Paint paint,
    206                         RectangleEdge position,
    207                         HorizontalAlignment horizontalAlignment,
    208                         VerticalAlignment verticalAlignment,
    209                         RectangleInsets padding) {
    210
    211            super(position, horizontalAlignment, verticalAlignment, padding);
    212
    213            if (text == null) {
    214                throw new NullPointerException("Null 'text' argument.");
    215            }
    216            if (font == null) {
    217                throw new NullPointerException("Null 'font' argument.");
    218            }
    219            if (paint == null) {
    220                throw new NullPointerException("Null 'paint' argument.");
    221            }
    222            this.text = text;
    223            this.font = font;
    224            this.paint = paint;
    225            // the textAlignment and the horizontalAlignment are separate things,
    226            // but it makes sense for the default textAlignment to match the
    227            // title's horizontal alignment...
    228            this.textAlignment = horizontalAlignment;
    229            this.backgroundPaint = null;
    230            this.content = null;
    231            this.toolTipText = null;
    232            this.urlText = null;
    233
    234        }
    235
    236        /**
    237         * Returns the title text.
    238         *
    239         * @return The text (never <code>null</code>).
    240         *
    241         * @see #setText(String)
    242         */
    243        public String getText() {
    244            return this.text;
    245        }
    246
    247        /**
    248         * Sets the title to the specified text and sends a
    249         * {@link TitleChangeEvent} to all registered listeners.
    250         *
    251         * @param text  the text (<code>null</code> not permitted).
    252         */
    253        public void setText(String text) {
    254            if (text == null) {
    255                throw new IllegalArgumentException("Null 'text' argument.");
    256            }
    257            if (!this.text.equals(text)) {
    258                this.text = text;
    259                notifyListeners(new TitleChangeEvent(this));
    260            }
    261        }
    262
    263        /**
    264         * Returns the text alignment.  This controls how the text is aligned
    265         * within the title's bounds, whereas the title's horizontal alignment
    266         * controls how the title's bounding rectangle is aligned within the
    267         * drawing space.
    268         *
    269         * @return The text alignment.
    270         */
    271        public HorizontalAlignment getTextAlignment() {
    272            return this.textAlignment;
    273        }
    274
    275        /**
    276         * Sets the text alignment.
    277         *
    278         * @param alignment  the alignment (<code>null</code> not permitted).
    279         */
    280        public void setTextAlignment(HorizontalAlignment alignment) {
    281            if (alignment == null) {
    282                throw new IllegalArgumentException("Null 'alignment' argument.");
    283            }
    284            this.textAlignment = alignment;
    285            notifyListeners(new TitleChangeEvent(this));
    286        }
    287
    288        /**
    289         * Returns the font used to display the title string.
    290         *
    291         * @return The font (never <code>null</code>).
    292         *
    293         * @see #setFont(Font)
    294         */
    295        public Font getFont() {
    296            return this.font;
    297        }
    298
    299        /**
    300         * Sets the font used to display the title string.  Registered listeners
    301         * are notified that the title has been modified.
    302         *
    303         * @param font  the new font (<code>null</code> not permitted).
    304         *
    305         * @see #getFont()
    306         */
    307        public void setFont(Font font) {
    308            if (font == null) {
    309                throw new IllegalArgumentException("Null 'font' argument.");
    310            }
    311            if (!this.font.equals(font)) {
    312                this.font = font;
    313                notifyListeners(new TitleChangeEvent(this));
    314            }
    315        }
    316
    317        /**
    318         * Returns the paint used to display the title string.
    319         *
    320         * @return The paint (never <code>null</code>).
    321         *
    322         * @see #setPaint(Paint)
    323         */
    324        public Paint getPaint() {
    325            return this.paint;
    326        }
    327
    328        /**
    329         * Sets the paint used to display the title string.  Registered listeners
    330         * are notified that the title has been modified.
    331         *
    332         * @param paint  the new paint (<code>null</code> not permitted).
    333         *
    334         * @see #getPaint()
    335         */
    336        public void setPaint(Paint paint) {
    337            if (paint == null) {
    338                throw new IllegalArgumentException("Null 'paint' argument.");
    339            }
    340            if (!this.paint.equals(paint)) {
    341                this.paint = paint;
    342                notifyListeners(new TitleChangeEvent(this));
    343            }
    344        }
    345
    346        /**
    347         * Returns the background paint.
    348         *
    349         * @return The paint (possibly <code>null</code>).
    350         */
    351        public Paint getBackgroundPaint() {
    352            return this.backgroundPaint;
    353        }
    354
    355        /**
    356         * Sets the background paint and sends a {@link TitleChangeEvent} to all
    357         * registered listeners.  If you set this attribute to <code>null</code>,
    358         * no background is painted (which makes the title background transparent).
    359         *
    360         * @param paint  the background paint (<code>null</code> permitted).
    361         */
    362        public void setBackgroundPaint(Paint paint) {
    363            this.backgroundPaint = paint;
    364            notifyListeners(new TitleChangeEvent(this));
    365        }
    366
    367        /**
    368         * Returns the tool tip text.
    369         *
    370         * @return The tool tip text (possibly <code>null</code>).
    371         */
    372        public String getToolTipText() {
    373            return this.toolTipText;
    374        }
    375
    376        /**
    377         * Sets the tool tip text to the specified text and sends a
    378         * {@link TitleChangeEvent} to all registered listeners.
    379         *
    380         * @param text  the text (<code>null</code> permitted).
    381         */
    382        public void setToolTipText(String text) {
    383            this.toolTipText = text;
    384            notifyListeners(new TitleChangeEvent(this));
    385        }
    386
    387        /**
    388         * Returns the URL text.
    389         *
    390         * @return The URL text (possibly <code>null</code>).
    391         */
    392        public String getURLText() {
    393            return this.urlText;
    394        }
    395
    396        /**
    397         * Sets the URL text to the specified text and sends a
    398         * {@link TitleChangeEvent} to all registered listeners.
    399         *
    400         * @param text  the text (<code>null</code> permitted).
    401         */
    402        public void setURLText(String text) {
    403            this.urlText = text;
    404            notifyListeners(new TitleChangeEvent(this));
    405        }
    406
    407        /**
    408         * Returns the flag that controls whether or not the title expands to fit
    409         * the available space.
    410         *
    411         * @return The flag.
    412         */
    413        public boolean getExpandToFitSpace() {
    414            return this.expandToFitSpace;
    415        }
    416
    417        /**
    418         * Sets the flag that controls whether the title expands to fit the
    419         * available space, and sends a {@link TitleChangeEvent} to all registered
    420         * listeners.
    421         *
    422         * @param expand  the flag.
    423         */
    424        public void setExpandToFitSpace(boolean expand) {
    425            this.expandToFitSpace = expand;
    426            notifyListeners(new TitleChangeEvent(this));
    427        }
    428
    429        /**
    430         * Arranges the contents of the block, within the given constraints, and
    431         * returns the block size.
    432         *
    433         * @param g2  the graphics device.
    434         * @param constraint  the constraint (<code>null</code> not permitted).
    435         *
    436         * @return The block size (in Java2D units, never <code>null</code>).
    437         */
    438        public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) {
    439            RectangleConstraint cc = toContentConstraint(constraint);
    440            LengthConstraintType w = cc.getWidthConstraintType();
    441            LengthConstraintType h = cc.getHeightConstraintType();
    442            Size2D contentSize = null;
    443            if (w == LengthConstraintType.NONE) {
    444                if (h == LengthConstraintType.NONE) {
    445                    contentSize = arrangeNN(g2);
    446                }
    447                else if (h == LengthConstraintType.RANGE) {
    448                    throw new RuntimeException("Not yet implemented.");
    449                }
    450                else if (h == LengthConstraintType.FIXED) {
    451                    throw new RuntimeException("Not yet implemented.");
    452                }
    453            }
    454            else if (w == LengthConstraintType.RANGE) {
    455                if (h == LengthConstraintType.NONE) {
    456                    contentSize = arrangeRN(g2, cc.getWidthRange());
    457                }
    458                else if (h == LengthConstraintType.RANGE) {
    459                    contentSize = arrangeRR(g2, cc.getWidthRange(),
    460                            cc.getHeightRange());
    461                }
    462                else if (h == LengthConstraintType.FIXED) {
    463                    throw new RuntimeException("Not yet implemented.");
    464                }
    465            }
    466            else if (w == LengthConstraintType.FIXED) {
    467                if (h == LengthConstraintType.NONE) {
    468                    contentSize = arrangeFN(g2, cc.getWidth());
    469                }
    470                else if (h == LengthConstraintType.RANGE) {
    471                    throw new RuntimeException("Not yet implemented.");
    472                }
    473                else if (h == LengthConstraintType.FIXED) {
    474                    throw new RuntimeException("Not yet implemented.");
    475                }
    476            }
    477            return new Size2D(calculateTotalWidth(contentSize.getWidth()),
    478                    calculateTotalHeight(contentSize.getHeight()));
    479        }
    480
    481        /**
    482         * Arranges the content for this title assuming no bounds on the width
    483         * or the height, and returns the required size.  This will reflect the
    484         * fact that a text title positioned on the left or right of a chart will
    485         * be rotated by 90 degrees.
    486         *
    487         * @param g2  the graphics target.
    488         *
    489         * @return The content size.
    490         *
    491         * @since 1.0.9
    492         */
    493        protected Size2D arrangeNN(Graphics2D g2) {
    494            Range max = new Range(0.0, Float.MAX_VALUE);
    495            return arrangeRR(g2, max, max);
    496        }
    497
    498        /**
    499         * Arranges the content for this title assuming a fixed width and no bounds
    500         * on the height, and returns the required size.  This will reflect the
    501         * fact that a text title positioned on the left or right of a chart will
    502         * be rotated by 90 degrees.
    503         *
    504         * @param g2  the graphics target.
    505         * @param w  the width.
    506         *
    507         * @return The content size.
    508         *
    509         * @since 1.0.9
    510         */
    511        protected Size2D arrangeFN(Graphics2D g2, double w) {
    512            RectangleEdge position = getPosition();
    513            if (position == RectangleEdge.TOP || position == RectangleEdge.BOTTOM) {
    514                float maxWidth = (float) w;
    515                g2.setFont(this.font);
    516                this.content = TextUtilities.createTextBlock(this.text, this.font,
    517                        this.paint, maxWidth, new G2TextMeasurer(g2));
    518                this.content.setLineAlignment(this.textAlignment);
    519                Size2D contentSize = this.content.calculateDimensions(g2);
    520                if (this.expandToFitSpace) {
    521                    return new Size2D(maxWidth, contentSize.getHeight());
    522                }
    523                else {
    524                    return contentSize;
    525                }
    526            }
    527            else if (position == RectangleEdge.LEFT || position
    528                    == RectangleEdge.RIGHT) {
    529                float maxWidth = Float.MAX_VALUE;
    530                g2.setFont(this.font);
    531                this.content = TextUtilities.createTextBlock(this.text, this.font,
    532                        this.paint, maxWidth, new G2TextMeasurer(g2));
    533                this.content.setLineAlignment(this.textAlignment);
    534                Size2D contentSize = this.content.calculateDimensions(g2);
    535
    536                // transpose the dimensions, because the title is rotated
    537                if (this.expandToFitSpace) {
    538                    return new Size2D(contentSize.getHeight(), maxWidth);
    539                }
    540                else {
    541                    return new Size2D(contentSize.height, contentSize.width);
    542                }
    543            }
    544            else {
    545                throw new RuntimeException("Unrecognised exception.");
    546            }
    547        }
    548
    549        /**
    550         * Arranges the content for this title assuming a range constraint for the
    551         * width and no bounds on the height, and returns the required size.  This
    552         * will reflect the fact that a text title positioned on the left or right
    553         * of a chart will be rotated by 90 degrees.
    554         *
    555         * @param g2  the graphics target.
    556         * @param widthRange  the range for the width.
    557         *
    558         * @return The content size.
    559         *
    560         * @since 1.0.9
    561         */
    562        protected Size2D arrangeRN(Graphics2D g2, Range widthRange) {
    563            Size2D s = arrangeNN(g2);
    564            if (widthRange.contains(s.getWidth())) {
    565                    return s;
    566            }
    567            double ww = widthRange.constrain(s.getWidth());
    568            return arrangeFN(g2, ww);
    569        }
    570
    571        /**
    572         * Returns the content size for the title.  This will reflect the fact that
    573         * a text title positioned on the left or right of a chart will be rotated
    574         * 90 degrees.
    575         *
    576         * @param g2  the graphics device.
    577         * @param widthRange  the width range.
    578         * @param heightRange  the height range.
    579         *
    580         * @return The content size.
    581         */
    582        protected Size2D arrangeRR(Graphics2D g2, Range widthRange,
    583                Range heightRange) {
    584            RectangleEdge position = getPosition();
    585            if (position == RectangleEdge.TOP || position == RectangleEdge.BOTTOM) {
    586                float maxWidth = (float) widthRange.getUpperBound();
    587                g2.setFont(this.font);
    588                this.content = TextUtilities.createTextBlock(this.text, this.font,
    589                        this.paint, maxWidth, new G2TextMeasurer(g2));
    590                this.content.setLineAlignment(this.textAlignment);
    591                Size2D contentSize = this.content.calculateDimensions(g2);
    592                if (this.expandToFitSpace) {
    593                    return new Size2D(maxWidth, contentSize.getHeight());
    594                }
    595                else {
    596                    return contentSize;
    597                }
    598            }
    599            else if (position == RectangleEdge.LEFT || position
    600                    == RectangleEdge.RIGHT) {
    601                float maxWidth = (float) heightRange.getUpperBound();
    602                g2.setFont(this.font);
    603                this.content = TextUtilities.createTextBlock(this.text, this.font,
    604                        this.paint, maxWidth, new G2TextMeasurer(g2));
    605                this.content.setLineAlignment(this.textAlignment);
    606                Size2D contentSize = this.content.calculateDimensions(g2);
    607
    608                // transpose the dimensions, because the title is rotated
    609                if (this.expandToFitSpace) {
    610                    return new Size2D(contentSize.getHeight(), maxWidth);
    611                }
    612                else {
    613                    return new Size2D(contentSize.height, contentSize.width);
    614                }
    615            }
    616            else {
    617                throw new RuntimeException("Unrecognised exception.");
    618            }
    619        }
    620
    621        /**
    622         * Draws the title on a Java 2D graphics device (such as the screen or a
    623         * printer).
    624         *
    625         * @param g2  the graphics device.
    626         * @param area  the area allocated for the title.
    627         */
    628        public void draw(Graphics2D g2, Rectangle2D area) {
    629            draw(g2, area, null);
    630        }
    631
    632        /**
    633         * Draws the block within the specified area.
    634         *
    635         * @param g2  the graphics device.
    636         * @param area  the area.
    637         * @param params  if this is an instance of {@link EntityBlockParams} it
    638         *                is used to determine whether or not an
    639         *                {@link EntityCollection} is returned by this method.
    640         *
    641         * @return An {@link EntityCollection} containing a chart entity for the
    642         *         title, or <code>null</code>.
    643         */
    644        public Object draw(Graphics2D g2, Rectangle2D area, Object params) {
    645            if (this.content == null) {
    646                return null;
    647            }
    648            area = trimMargin(area);
    649            drawBorder(g2, area);
    650            if (this.text.equals("")) {
    651                return null;
    652            }
    653            ChartEntity entity = null;
    654            if (params instanceof EntityBlockParams) {
    655                EntityBlockParams p = (EntityBlockParams) params;
    656                if (p.getGenerateEntities()) {
    657                    entity = new ChartEntity(area, this.toolTipText, this.urlText);
    658                }
    659            }
    660            area = trimBorder(area);
    661            if (this.backgroundPaint != null) {
    662                g2.setPaint(this.backgroundPaint);
    663                g2.fill(area);
    664            }
    665            area = trimPadding(area);
    666            RectangleEdge position = getPosition();
    667            if (position == RectangleEdge.TOP || position == RectangleEdge.BOTTOM) {
    668                drawHorizontal(g2, area);
    669            }
    670            else if (position == RectangleEdge.LEFT
    671                     || position == RectangleEdge.RIGHT) {
    672                drawVertical(g2, area);
    673            }
    674            BlockResult result = new BlockResult();
    675            if (entity != null) {
    676                StandardEntityCollection sec = new StandardEntityCollection();
    677                sec.add(entity);
    678                result.setEntityCollection(sec);
    679            }
    680            return result;
    681        }
    682
    683        /**
    684         * Draws a the title horizontally within the specified area.  This method
    685         * will be called from the {@link #draw(Graphics2D, Rectangle2D) draw}
    686         * method.
    687         *
    688         * @param g2  the graphics device.
    689         * @param area  the area for the title.
    690         */
    691        protected void drawHorizontal(Graphics2D g2, Rectangle2D area) {
    692            Rectangle2D titleArea = (Rectangle2D) area.clone();
    693            g2.setFont(this.font);
    694            g2.setPaint(this.paint);
    695            TextBlockAnchor anchor = null;
    696            float x = 0.0f;
    697            HorizontalAlignment horizontalAlignment = getHorizontalAlignment();
    698            if (horizontalAlignment == HorizontalAlignment.LEFT) {
    699                x = (float) titleArea.getX();
    700                anchor = TextBlockAnchor.TOP_LEFT;
    701            }
    702            else if (horizontalAlignment == HorizontalAlignment.RIGHT) {
    703                x = (float) titleArea.getMaxX();
    704                anchor = TextBlockAnchor.TOP_RIGHT;
    705            }
    706            else if (horizontalAlignment == HorizontalAlignment.CENTER) {
    707                x = (float) titleArea.getCenterX();
    708                anchor = TextBlockAnchor.TOP_CENTER;
    709            }
    710            float y = 0.0f;
    711            RectangleEdge position = getPosition();
    712            if (position == RectangleEdge.TOP) {
    713                y = (float) titleArea.getY();
    714            }
    715            else if (position == RectangleEdge.BOTTOM) {
    716                y = (float) titleArea.getMaxY();
    717                if (horizontalAlignment == HorizontalAlignment.LEFT) {
    718                    anchor = TextBlockAnchor.BOTTOM_LEFT;
    719                }
    720                else if (horizontalAlignment == HorizontalAlignment.CENTER) {
    721                    anchor = TextBlockAnchor.BOTTOM_CENTER;
    722                }
    723                else if (horizontalAlignment == HorizontalAlignment.RIGHT) {
    724                    anchor = TextBlockAnchor.BOTTOM_RIGHT;
    725                }
    726            }
    727            this.content.draw(g2, x, y, anchor);
    728        }
    729
    730        /**
    731         * Draws a the title vertically 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 drawVertical(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 y = 0.0f;
    744            VerticalAlignment verticalAlignment = getVerticalAlignment();
    745            if (verticalAlignment == VerticalAlignment.TOP) {
    746                y = (float) titleArea.getY();
    747                anchor = TextBlockAnchor.TOP_RIGHT;
    748            }
    749            else if (verticalAlignment == VerticalAlignment.BOTTOM) {
    750                y = (float) titleArea.getMaxY();
    751                anchor = TextBlockAnchor.TOP_LEFT;
    752            }
    753            else if (verticalAlignment == VerticalAlignment.CENTER) {
    754                y = (float) titleArea.getCenterY();
    755                anchor = TextBlockAnchor.TOP_CENTER;
    756            }
    757            float x = 0.0f;
    758            RectangleEdge position = getPosition();
    759            if (position == RectangleEdge.LEFT) {
    760                x = (float) titleArea.getX();
    761            }
    762            else if (position == RectangleEdge.RIGHT) {
    763                x = (float) titleArea.getMaxX();
    764                if (verticalAlignment == VerticalAlignment.TOP) {
    765                    anchor = TextBlockAnchor.BOTTOM_RIGHT;
    766                }
    767                else if (verticalAlignment == VerticalAlignment.CENTER) {
    768                    anchor = TextBlockAnchor.BOTTOM_CENTER;
    769                }
    770                else if (verticalAlignment == VerticalAlignment.BOTTOM) {
    771                    anchor = TextBlockAnchor.BOTTOM_LEFT;
    772                }
    773            }
    774            this.content.draw(g2, x, y, anchor, x, y, -Math.PI / 2.0);
    775        }
    776
    777        /**
    778         * Tests this title for equality with another object.
    779         *
    780         * @param obj  the object (<code>null</code> permitted).
    781         *
    782         * @return <code>true</code> or <code>false</code>.
    783         */
    784        public boolean equals(Object obj) {
    785            if (obj == this) {
    786                return true;
    787            }
    788            if (!(obj instanceof TextTitle)) {
    789                return false;
    790            }
    791            if (!super.equals(obj)) {
    792                return false;
    793            }
    794            TextTitle that = (TextTitle) obj;
    795            if (!ObjectUtilities.equal(this.text, that.text)) {
    796                return false;
    797            }
    798            if (!ObjectUtilities.equal(this.font, that.font)) {
    799                return false;
    800            }
    801            if (!PaintUtilities.equal(this.paint, that.paint)) {
    802                return false;
    803            }
    804            if (this.textAlignment != that.textAlignment) {
    805                return false;
    806            }
    807            if (!PaintUtilities.equal(this.backgroundPaint, that.backgroundPaint)) {
    808                return false;
    809            }
    810            return true;
    811        }
    812
    813        /**
    814         * Returns a hash code.
    815         *
    816         * @return A hash code.
    817         */
    818        public int hashCode() {
    819            int result = super.hashCode();
    820            result = 29 * result + (this.text != null ? this.text.hashCode() : 0);
    821            result = 29 * result + (this.font != null ? this.font.hashCode() : 0);
    822            result = 29 * result + (this.paint != null ? this.paint.hashCode() : 0);
    823            result = 29 * result + (this.backgroundPaint != null
    824                    ? this.backgroundPaint.hashCode() : 0);
    825            return result;
    826        }
    827
    828        /**
    829         * Returns a clone of this object.
    830         *
    831         * @return A clone.
    832         *
    833         * @throws CloneNotSupportedException never.
    834         */
    835        public Object clone() throws CloneNotSupportedException {
    836            return super.clone();
    837        }
    838
    839        /**
    840         * Provides serialization support.
    841         *
    842         * @param stream  the output stream.
    843         *
    844         * @throws IOException  if there is an I/O error.
    845         */
    846        private void writeObject(ObjectOutputStream stream) throws IOException {
    847            stream.defaultWriteObject();
    848            SerialUtilities.writePaint(this.paint, stream);
    849            SerialUtilities.writePaint(this.backgroundPaint, stream);
    850        }
    851
    852        /**
    853         * Provides serialization support.
    854         *
    855         * @param stream  the input stream.
    856         *
    857         * @throws IOException  if there is an I/O error.
    858         * @throws ClassNotFoundException  if there is a classpath problem.
    859         */
    860        private void readObject(ObjectInputStream stream)
    861            throws IOException, ClassNotFoundException
    862        {
    863            stream.defaultReadObject();
    864            this.paint = SerialUtilities.readPaint(stream);
    865            this.backgroundPaint = SerialUtilities.readPaint(stream);
    866        }
    867
    868    }
    869