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