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