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