001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2008, 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-2008, by David Berry and Contributors.
031     *
032     * Original Author:  David Berry;
033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
034     *                   Nicolas Brodu;
035     *
036     * Changes (from 18-Sep-2001)
037     * --------------------------
038     * 18-Sep-2001 : Added standard header (DG);
039     * 07-Nov-2001 : Separated the JCommon Class Library classes, JFreeChart now
040     *               requires jcommon.jar (DG);
041     * 09-Jan-2002 : Updated Javadoc comments (DG);
042     * 07-Feb-2002 : Changed Insets --> Spacer in AbstractTitle.java (DG);
043     * 06-Mar-2002 : Updated import statements (DG);
044     * 25-Jun-2002 : Removed redundant imports (DG);
045     * 18-Sep-2002 : Fixed errors reported by Checkstyle (DG);
046     * 28-Oct-2002 : Small modifications while changing JFreeChart class (DG);
047     * 13-Mar-2003 : Changed width used for relative spacing to fix bug 703050 (DG);
048     * 26-Mar-2003 : Implemented Serializable (DG);
049     * 15-Jul-2003 : Fixed null pointer exception (DG);
050     * 11-Sep-2003 : Implemented Cloneable (NB)
051     * 22-Sep-2003 : Added checks for null values and throw nullpointer
052     *               exceptions (TM);
053     *               Background paint was not serialized.
054     * 07-Oct-2003 : Added fix for exception caused by empty string in title (DG);
055     * 29-Oct-2003 : Added workaround for text alignment in PDF output (DG);
056     * 03-Feb-2004 : Fixed bug in getPreferredWidth() method (DG);
057     * 17-Feb-2004 : Added clone() method and fixed bug in equals() method (DG);
058     * 01-Apr-2004 : Changed java.awt.geom.Dimension2D to org.jfree.ui.Size2D
059     *               because of JDK bug 4976448 which persists on JDK 1.3.1.  Also
060     *               fixed bug in getPreferredHeight() method (DG);
061     * 29-Apr-2004 : Fixed bug in getPreferredWidth() method - see bug id
062     *               944173 (DG);
063     * 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0
064     *               release (DG);
065     * 08-Feb-2005 : Updated for changes in RectangleConstraint class (DG);
066     * 11-Feb-2005 : Implemented PublicCloneable (DG);
067     * 20-Apr-2005 : Added support for tooltips (DG);
068     * 26-Apr-2005 : Removed LOGGER (DG);
069     * 06-Jun-2005 : Modified equals() to handle GradientPaint (DG);
070     * 06-Jul-2005 : Added flag to control whether or not the title expands to
071     *               fit the available space (DG);
072     * 07-Oct-2005 : Added textAlignment attribute (DG);
073     * ------------- JFREECHART 1.0.x RELEASED ------------------------------------
074     * 13-Dec-2005 : Fixed bug 1379331 - incorrect drawing with LEFT or RIGHT
075     *               title placement (DG);
076     * 19-Dec-2007 : Implemented some of the missing arrangement options (DG);
077     * 28-Apr-2008 : Added option for maximum lines, and fixed minor bugs in
078     *               equals() method (DG);
079     *
080     */
081    
082    package org.jfree.chart.title;
083    
084    import java.awt.Color;
085    import java.awt.Font;
086    import java.awt.Graphics2D;
087    import java.awt.Paint;
088    import java.awt.geom.Rectangle2D;
089    import java.io.IOException;
090    import java.io.ObjectInputStream;
091    import java.io.ObjectOutputStream;
092    import java.io.Serializable;
093    
094    import org.jfree.chart.block.BlockResult;
095    import org.jfree.chart.block.EntityBlockParams;
096    import org.jfree.chart.block.LengthConstraintType;
097    import org.jfree.chart.block.RectangleConstraint;
098    import org.jfree.chart.entity.ChartEntity;
099    import org.jfree.chart.entity.EntityCollection;
100    import org.jfree.chart.entity.StandardEntityCollection;
101    import org.jfree.chart.event.TitleChangeEvent;
102    import org.jfree.data.Range;
103    import org.jfree.io.SerialUtilities;
104    import org.jfree.text.G2TextMeasurer;
105    import org.jfree.text.TextBlock;
106    import org.jfree.text.TextBlockAnchor;
107    import org.jfree.text.TextUtilities;
108    import org.jfree.ui.HorizontalAlignment;
109    import org.jfree.ui.RectangleEdge;
110    import org.jfree.ui.RectangleInsets;
111    import org.jfree.ui.Size2D;
112    import org.jfree.ui.VerticalAlignment;
113    import org.jfree.util.ObjectUtilities;
114    import org.jfree.util.PaintUtilities;
115    import org.jfree.util.PublicCloneable;
116    
117    /**
118     * A chart title that displays a text string with automatic wrapping as
119     * required.
120     */
121    public class TextTitle extends Title
122                           implements Serializable, Cloneable, PublicCloneable {
123    
124        /** For serialization. */
125        private static final long serialVersionUID = 8372008692127477443L;
126    
127        /** The default font. */
128        public static final Font DEFAULT_FONT = new Font("SansSerif", Font.BOLD,
129                12);
130    
131        /** The default text color. */
132        public static final Paint DEFAULT_TEXT_PAINT = Color.black;
133    
134        /** The title text. */
135        private String text;
136    
137        /** The font used to display the title. */
138        private Font font;
139    
140        /** The text alignment. */
141        private HorizontalAlignment textAlignment;
142    
143        /** The paint used to display the title text. */
144        private transient Paint paint;
145    
146        /** The background paint. */
147        private transient Paint backgroundPaint;
148    
149        /** The tool tip text (can be <code>null</code>). */
150        private String toolTipText;
151    
152        /** The URL text (can be <code>null</code>). */
153        private String urlText;
154    
155        /** The content. */
156        private TextBlock content;
157    
158        /**
159         * A flag that controls whether the title expands to fit the available
160         * space..
161         */
162        private boolean expandToFitSpace = false;
163    
164        /**
165         * The maximum number of lines to display.
166         *
167         * @since 1.0.10
168         */
169        private int maximumLinesToDisplay = Integer.MAX_VALUE;
170    
171        /**
172         * Creates a new title, using default attributes where necessary.
173         */
174        public TextTitle() {
175            this("");
176        }
177    
178        /**
179         * Creates a new title, using default attributes where necessary.
180         *
181         * @param text  the title text (<code>null</code> not permitted).
182         */
183        public TextTitle(String text) {
184            this(text, TextTitle.DEFAULT_FONT, TextTitle.DEFAULT_TEXT_PAINT,
185                    Title.DEFAULT_POSITION, Title.DEFAULT_HORIZONTAL_ALIGNMENT,
186                    Title.DEFAULT_VERTICAL_ALIGNMENT, Title.DEFAULT_PADDING);
187        }
188    
189        /**
190         * Creates a new title, using default attributes where necessary.
191         *
192         * @param text  the title text (<code>null</code> not permitted).
193         * @param font  the title font (<code>null</code> not permitted).
194         */
195        public TextTitle(String text, Font font) {
196            this(text, font, TextTitle.DEFAULT_TEXT_PAINT, Title.DEFAULT_POSITION,
197                    Title.DEFAULT_HORIZONTAL_ALIGNMENT,
198                    Title.DEFAULT_VERTICAL_ALIGNMENT, Title.DEFAULT_PADDING);
199        }
200    
201        /**
202         * Creates a new title.
203         *
204         * @param text  the text for the title (<code>null</code> not permitted).
205         * @param font  the title font (<code>null</code> not permitted).
206         * @param paint  the title paint (<code>null</code> not permitted).
207         * @param position  the title position (<code>null</code> not permitted).
208         * @param horizontalAlignment  the horizontal alignment (<code>null</code>
209         *                             not permitted).
210         * @param verticalAlignment  the vertical alignment (<code>null</code> not
211         *                           permitted).
212         * @param padding  the space to leave around the outside of the title.
213         */
214        public TextTitle(String text, Font font, Paint paint,
215                         RectangleEdge position,
216                         HorizontalAlignment horizontalAlignment,
217                         VerticalAlignment verticalAlignment,
218                         RectangleInsets padding) {
219    
220            super(position, horizontalAlignment, verticalAlignment, padding);
221    
222            if (text == null) {
223                throw new NullPointerException("Null 'text' argument.");
224            }
225            if (font == null) {
226                throw new NullPointerException("Null 'font' argument.");
227            }
228            if (paint == null) {
229                throw new NullPointerException("Null 'paint' argument.");
230            }
231            this.text = text;
232            this.font = font;
233            this.paint = paint;
234            // the textAlignment and the horizontalAlignment are separate things,
235            // but it makes sense for the default textAlignment to match the
236            // title's horizontal alignment...
237            this.textAlignment = horizontalAlignment;
238            this.backgroundPaint = null;
239            this.content = null;
240            this.toolTipText = null;
241            this.urlText = null;
242    
243        }
244    
245        /**
246         * Returns the title text.
247         *
248         * @return The text (never <code>null</code>).
249         *
250         * @see #setText(String)
251         */
252        public String getText() {
253            return this.text;
254        }
255    
256        /**
257         * Sets the title to the specified text and sends a
258         * {@link TitleChangeEvent} to all registered listeners.
259         *
260         * @param text  the text (<code>null</code> not permitted).
261         */
262        public void setText(String text) {
263            if (text == null) {
264                throw new IllegalArgumentException("Null 'text' argument.");
265            }
266            if (!this.text.equals(text)) {
267                this.text = text;
268                notifyListeners(new TitleChangeEvent(this));
269            }
270        }
271    
272        /**
273         * Returns the text alignment.  This controls how the text is aligned
274         * within the title's bounds, whereas the title's horizontal alignment
275         * controls how the title's bounding rectangle is aligned within the
276         * drawing space.
277         *
278         * @return The text alignment.
279         */
280        public HorizontalAlignment getTextAlignment() {
281            return this.textAlignment;
282        }
283    
284        /**
285         * Sets the text alignment and sends a {@link TitleChangeEvent} to
286         * all registered listeners.
287         *
288         * @param alignment  the alignment (<code>null</code> not permitted).
289         */
290        public void setTextAlignment(HorizontalAlignment alignment) {
291            if (alignment == null) {
292                throw new IllegalArgumentException("Null 'alignment' argument.");
293            }
294            this.textAlignment = alignment;
295            notifyListeners(new TitleChangeEvent(this));
296        }
297    
298        /**
299         * Returns the font used to display the title string.
300         *
301         * @return The font (never <code>null</code>).
302         *
303         * @see #setFont(Font)
304         */
305        public Font getFont() {
306            return this.font;
307        }
308    
309        /**
310         * Sets the font used to display the title string.  Registered listeners
311         * are notified that the title has been modified.
312         *
313         * @param font  the new font (<code>null</code> not permitted).
314         *
315         * @see #getFont()
316         */
317        public void setFont(Font font) {
318            if (font == null) {
319                throw new IllegalArgumentException("Null 'font' argument.");
320            }
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            if (paint == null) {
348                throw new IllegalArgumentException("Null 'paint' argument.");
349            }
350            if (!this.paint.equals(paint)) {
351                this.paint = paint;
352                notifyListeners(new TitleChangeEvent(this));
353            }
354        }
355    
356        /**
357         * Returns the background paint.
358         *
359         * @return The paint (possibly <code>null</code>).
360         */
361        public Paint getBackgroundPaint() {
362            return this.backgroundPaint;
363        }
364    
365        /**
366         * Sets the background paint and sends a {@link TitleChangeEvent} to all
367         * registered listeners.  If you set this attribute to <code>null</code>,
368         * no background is painted (which makes the title background transparent).
369         *
370         * @param paint  the background paint (<code>null</code> permitted).
371         */
372        public void setBackgroundPaint(Paint paint) {
373            this.backgroundPaint = paint;
374            notifyListeners(new TitleChangeEvent(this));
375        }
376    
377        /**
378         * Returns the tool tip text.
379         *
380         * @return The tool tip text (possibly <code>null</code>).
381         */
382        public String getToolTipText() {
383            return this.toolTipText;
384        }
385    
386        /**
387         * Sets the tool tip text to the specified text and sends a
388         * {@link TitleChangeEvent} to all registered listeners.
389         *
390         * @param text  the text (<code>null</code> permitted).
391         */
392        public void setToolTipText(String text) {
393            this.toolTipText = text;
394            notifyListeners(new TitleChangeEvent(this));
395        }
396    
397        /**
398         * Returns the URL text.
399         *
400         * @return The URL text (possibly <code>null</code>).
401         */
402        public String getURLText() {
403            return this.urlText;
404        }
405    
406        /**
407         * Sets the URL text to the specified text and sends a
408         * {@link TitleChangeEvent} to all registered listeners.
409         *
410         * @param text  the text (<code>null</code> permitted).
411         */
412        public void setURLText(String text) {
413            this.urlText = text;
414            notifyListeners(new TitleChangeEvent(this));
415        }
416    
417        /**
418         * Returns the flag that controls whether or not the title expands to fit
419         * the available space.
420         *
421         * @return The flag.
422         */
423        public boolean getExpandToFitSpace() {
424            return this.expandToFitSpace;
425        }
426    
427        /**
428         * Sets the flag that controls whether the title expands to fit the
429         * available space, and sends a {@link TitleChangeEvent} to all registered
430         * listeners.
431         *
432         * @param expand  the flag.
433         */
434        public void setExpandToFitSpace(boolean expand) {
435            this.expandToFitSpace = expand;
436            notifyListeners(new TitleChangeEvent(this));
437        }
438    
439        /**
440         * Returns the maximum number of lines to display.
441         *
442         * @return The maximum.
443         *
444         * @since 1.0.10
445         *
446         * @see #setMaximumLinesToDisplay(int)
447         */
448        public int getMaximumLinesToDisplay() {
449            return this.maximumLinesToDisplay;
450        }
451    
452        /**
453         * Sets the maximum number of lines to display and sends a
454         * {@link TitleChangeEvent} to all registered listeners.
455         *
456         * @param max  the maximum.
457         *
458         * @since 1.0.10.
459         *
460         * @see #getMaximumLinesToDisplay()
461         */
462        public void setMaximumLinesToDisplay(int max) {
463            this.maximumLinesToDisplay = max;
464            notifyListeners(new TitleChangeEvent(this));
465        }
466    
467        /**
468         * Arranges the contents of the block, within the given constraints, and
469         * returns the block size.
470         *
471         * @param g2  the graphics device.
472         * @param constraint  the constraint (<code>null</code> not permitted).
473         *
474         * @return The block size (in Java2D units, never <code>null</code>).
475         */
476        public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) {
477            RectangleConstraint cc = toContentConstraint(constraint);
478            LengthConstraintType w = cc.getWidthConstraintType();
479            LengthConstraintType h = cc.getHeightConstraintType();
480            Size2D contentSize = null;
481            if (w == LengthConstraintType.NONE) {
482                if (h == LengthConstraintType.NONE) {
483                    contentSize = arrangeNN(g2);
484                }
485                else if (h == LengthConstraintType.RANGE) {
486                    throw new RuntimeException("Not yet implemented.");
487                }
488                else if (h == LengthConstraintType.FIXED) {
489                    throw new RuntimeException("Not yet implemented.");
490                }
491            }
492            else if (w == LengthConstraintType.RANGE) {
493                if (h == LengthConstraintType.NONE) {
494                    contentSize = arrangeRN(g2, cc.getWidthRange());
495                }
496                else if (h == LengthConstraintType.RANGE) {
497                    contentSize = arrangeRR(g2, cc.getWidthRange(),
498                            cc.getHeightRange());
499                }
500                else if (h == LengthConstraintType.FIXED) {
501                    throw new RuntimeException("Not yet implemented.");
502                }
503            }
504            else if (w == LengthConstraintType.FIXED) {
505                if (h == LengthConstraintType.NONE) {
506                    contentSize = arrangeFN(g2, cc.getWidth());
507                }
508                else if (h == LengthConstraintType.RANGE) {
509                    throw new RuntimeException("Not yet implemented.");
510                }
511                else if (h == LengthConstraintType.FIXED) {
512                    throw new RuntimeException("Not yet implemented.");
513                }
514            }
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        public void draw(Graphics2D g2, Rectangle2D area) {
671            draw(g2, area, null);
672        }
673    
674        /**
675         * Draws the block within the specified area.
676         *
677         * @param g2  the graphics device.
678         * @param area  the area.
679         * @param params  if this is an instance of {@link EntityBlockParams} it
680         *                is used to determine whether or not an
681         *                {@link EntityCollection} is returned by this method.
682         *
683         * @return An {@link EntityCollection} containing a chart entity for the
684         *         title, or <code>null</code>.
685         */
686        public Object draw(Graphics2D g2, Rectangle2D area, Object params) {
687            if (this.content == null) {
688                return null;
689            }
690            area = trimMargin(area);
691            drawBorder(g2, area);
692            if (this.text.equals("")) {
693                return null;
694            }
695            ChartEntity entity = null;
696            if (params instanceof EntityBlockParams) {
697                EntityBlockParams p = (EntityBlockParams) params;
698                if (p.getGenerateEntities()) {
699                    entity = new ChartEntity(area, this.toolTipText, this.urlText);
700                }
701            }
702            area = trimBorder(area);
703            if (this.backgroundPaint != null) {
704                g2.setPaint(this.backgroundPaint);
705                g2.fill(area);
706            }
707            area = trimPadding(area);
708            RectangleEdge position = getPosition();
709            if (position == RectangleEdge.TOP || position == RectangleEdge.BOTTOM) {
710                drawHorizontal(g2, area);
711            }
712            else if (position == RectangleEdge.LEFT
713                     || position == RectangleEdge.RIGHT) {
714                drawVertical(g2, area);
715            }
716            BlockResult result = new BlockResult();
717            if (entity != null) {
718                StandardEntityCollection sec = new StandardEntityCollection();
719                sec.add(entity);
720                result.setEntityCollection(sec);
721            }
722            return result;
723        }
724    
725        /**
726         * Draws a the title horizontally within the specified area.  This method
727         * will be called from the {@link #draw(Graphics2D, Rectangle2D) draw}
728         * method.
729         *
730         * @param g2  the graphics device.
731         * @param area  the area for the title.
732         */
733        protected void drawHorizontal(Graphics2D g2, Rectangle2D area) {
734            Rectangle2D titleArea = (Rectangle2D) area.clone();
735            g2.setFont(this.font);
736            g2.setPaint(this.paint);
737            TextBlockAnchor anchor = null;
738            float x = 0.0f;
739            HorizontalAlignment horizontalAlignment = getHorizontalAlignment();
740            if (horizontalAlignment == HorizontalAlignment.LEFT) {
741                x = (float) titleArea.getX();
742                anchor = TextBlockAnchor.TOP_LEFT;
743            }
744            else if (horizontalAlignment == HorizontalAlignment.RIGHT) {
745                x = (float) titleArea.getMaxX();
746                anchor = TextBlockAnchor.TOP_RIGHT;
747            }
748            else if (horizontalAlignment == HorizontalAlignment.CENTER) {
749                x = (float) titleArea.getCenterX();
750                anchor = TextBlockAnchor.TOP_CENTER;
751            }
752            float y = 0.0f;
753            RectangleEdge position = getPosition();
754            if (position == RectangleEdge.TOP) {
755                y = (float) titleArea.getY();
756            }
757            else if (position == RectangleEdge.BOTTOM) {
758                y = (float) titleArea.getMaxY();
759                if (horizontalAlignment == HorizontalAlignment.LEFT) {
760                    anchor = TextBlockAnchor.BOTTOM_LEFT;
761                }
762                else if (horizontalAlignment == HorizontalAlignment.CENTER) {
763                    anchor = TextBlockAnchor.BOTTOM_CENTER;
764                }
765                else if (horizontalAlignment == HorizontalAlignment.RIGHT) {
766                    anchor = TextBlockAnchor.BOTTOM_RIGHT;
767                }
768            }
769            this.content.draw(g2, x, y, anchor);
770        }
771    
772        /**
773         * Draws a the title vertically within the specified area.  This method
774         * will be called from the {@link #draw(Graphics2D, Rectangle2D) draw}
775         * method.
776         *
777         * @param g2  the graphics device.
778         * @param area  the area for the title.
779         */
780        protected void drawVertical(Graphics2D g2, Rectangle2D area) {
781            Rectangle2D titleArea = (Rectangle2D) area.clone();
782            g2.setFont(this.font);
783            g2.setPaint(this.paint);
784            TextBlockAnchor anchor = null;
785            float y = 0.0f;
786            VerticalAlignment verticalAlignment = getVerticalAlignment();
787            if (verticalAlignment == VerticalAlignment.TOP) {
788                y = (float) titleArea.getY();
789                anchor = TextBlockAnchor.TOP_RIGHT;
790            }
791            else if (verticalAlignment == VerticalAlignment.BOTTOM) {
792                y = (float) titleArea.getMaxY();
793                anchor = TextBlockAnchor.TOP_LEFT;
794            }
795            else if (verticalAlignment == VerticalAlignment.CENTER) {
796                y = (float) titleArea.getCenterY();
797                anchor = TextBlockAnchor.TOP_CENTER;
798            }
799            float x = 0.0f;
800            RectangleEdge position = getPosition();
801            if (position == RectangleEdge.LEFT) {
802                x = (float) titleArea.getX();
803            }
804            else if (position == RectangleEdge.RIGHT) {
805                x = (float) titleArea.getMaxX();
806                if (verticalAlignment == VerticalAlignment.TOP) {
807                    anchor = TextBlockAnchor.BOTTOM_RIGHT;
808                }
809                else if (verticalAlignment == VerticalAlignment.CENTER) {
810                    anchor = TextBlockAnchor.BOTTOM_CENTER;
811                }
812                else if (verticalAlignment == VerticalAlignment.BOTTOM) {
813                    anchor = TextBlockAnchor.BOTTOM_LEFT;
814                }
815            }
816            this.content.draw(g2, x, y, anchor, x, y, -Math.PI / 2.0);
817        }
818    
819        /**
820         * Tests this title for equality with another object.
821         *
822         * @param obj  the object (<code>null</code> permitted).
823         *
824         * @return <code>true</code> or <code>false</code>.
825         */
826        public boolean equals(Object obj) {
827            if (obj == this) {
828                return true;
829            }
830            if (!(obj instanceof TextTitle)) {
831                return false;
832            }
833            TextTitle that = (TextTitle) obj;
834            if (!ObjectUtilities.equal(this.text, that.text)) {
835                return false;
836            }
837            if (!ObjectUtilities.equal(this.font, that.font)) {
838                return false;
839            }
840            if (!PaintUtilities.equal(this.paint, that.paint)) {
841                return false;
842            }
843            if (this.textAlignment != that.textAlignment) {
844                return false;
845            }
846            if (!PaintUtilities.equal(this.backgroundPaint, that.backgroundPaint)) {
847                return false;
848            }
849            if (this.maximumLinesToDisplay != that.maximumLinesToDisplay) {
850                return false;
851            }
852            if (this.expandToFitSpace != that.expandToFitSpace) {
853                return false;
854            }
855            if (!ObjectUtilities.equal(this.toolTipText, that.toolTipText)) {
856                return false;
857            }
858            if (!ObjectUtilities.equal(this.urlText, that.urlText)) {
859                return false;
860            }
861            return super.equals(obj);
862        }
863    
864        /**
865         * Returns a hash code.
866         *
867         * @return A hash code.
868         */
869        public int hashCode() {
870            int result = super.hashCode();
871            result = 29 * result + (this.text != null ? this.text.hashCode() : 0);
872            result = 29 * result + (this.font != null ? this.font.hashCode() : 0);
873            result = 29 * result + (this.paint != null ? this.paint.hashCode() : 0);
874            result = 29 * result + (this.backgroundPaint != null
875                    ? this.backgroundPaint.hashCode() : 0);
876            return result;
877        }
878    
879        /**
880         * Returns a clone of this object.
881         *
882         * @return A clone.
883         *
884         * @throws CloneNotSupportedException never.
885         */
886        public Object clone() throws CloneNotSupportedException {
887            return super.clone();
888        }
889    
890        /**
891         * Provides serialization support.
892         *
893         * @param stream  the output stream.
894         *
895         * @throws IOException  if there is an I/O error.
896         */
897        private void writeObject(ObjectOutputStream stream) throws IOException {
898            stream.defaultWriteObject();
899            SerialUtilities.writePaint(this.paint, stream);
900            SerialUtilities.writePaint(this.backgroundPaint, stream);
901        }
902    
903        /**
904         * Provides serialization support.
905         *
906         * @param stream  the input stream.
907         *
908         * @throws IOException  if there is an I/O error.
909         * @throws ClassNotFoundException  if there is a classpath problem.
910         */
911        private void readObject(ObjectInputStream stream)
912                throws IOException, ClassNotFoundException {
913            stream.defaultReadObject();
914            this.paint = SerialUtilities.readPaint(stream);
915            this.backgroundPaint = SerialUtilities.readPaint(stream);
916        }
917    
918    }
919