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