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    
085    package org.jfree.chart.title;
086    
087    import java.awt.Color;
088    import java.awt.Font;
089    import java.awt.Graphics2D;
090    import java.awt.Paint;
091    import java.awt.geom.Rectangle2D;
092    import java.io.IOException;
093    import java.io.ObjectInputStream;
094    import java.io.ObjectOutputStream;
095    import java.io.Serializable;
096    
097    import org.jfree.chart.block.BlockResult;
098    import org.jfree.chart.block.EntityBlockParams;
099    import org.jfree.chart.block.LengthConstraintType;
100    import org.jfree.chart.block.RectangleConstraint;
101    import org.jfree.chart.entity.ChartEntity;
102    import org.jfree.chart.entity.EntityCollection;
103    import org.jfree.chart.entity.StandardEntityCollection;
104    import org.jfree.chart.entity.TitleEntity;
105    import org.jfree.chart.event.TitleChangeEvent;
106    import org.jfree.data.Range;
107    import org.jfree.io.SerialUtilities;
108    import org.jfree.text.G2TextMeasurer;
109    import org.jfree.text.TextBlock;
110    import org.jfree.text.TextBlockAnchor;
111    import org.jfree.text.TextUtilities;
112    import org.jfree.ui.HorizontalAlignment;
113    import org.jfree.ui.RectangleEdge;
114    import org.jfree.ui.RectangleInsets;
115    import org.jfree.ui.Size2D;
116    import org.jfree.ui.VerticalAlignment;
117    import org.jfree.util.ObjectUtilities;
118    import org.jfree.util.PaintUtilities;
119    import org.jfree.util.PublicCloneable;
120    
121    /**
122     * A chart title that displays a text string with automatic wrapping as
123     * required.
124     */
125    public 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