001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2005, 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-2005, 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     * $Id: TextTitle.java,v 1.16.2.5 2005/10/25 20:58:34 mungady Exp $
037     *
038     * Changes (from 18-Sep-2001)
039     * --------------------------
040     * 18-Sep-2001 : Added standard header (DG);
041     * 07-Nov-2001 : Separated the JCommon Class Library classes, JFreeChart now 
042     *               requires jcommon.jar (DG);
043     * 09-Jan-2002 : Updated Javadoc comments (DG);
044     * 07-Feb-2002 : Changed Insets --> Spacer in AbstractTitle.java (DG);
045     * 06-Mar-2002 : Updated import statements (DG);
046     * 25-Jun-2002 : Removed redundant imports (DG);
047     * 18-Sep-2002 : Fixed errors reported by Checkstyle (DG);
048     * 28-Oct-2002 : Small modifications while changing JFreeChart class (DG);
049     * 13-Mar-2003 : Changed width used for relative spacing to fix bug 703050 (DG);
050     * 26-Mar-2003 : Implemented Serializable (DG);
051     * 15-Jul-2003 : Fixed null pointer exception (DG);
052     * 11-Sep-2003 : Implemented Cloneable (NB)
053     * 22-Sep-2003 : Added checks for null values and throw nullpointer 
054     *               exceptions (TM); 
055     *               Background paint was not serialized.
056     * 07-Oct-2003 : Added fix for exception caused by empty string in title (DG);
057     * 29-Oct-2003 : Added workaround for text alignment in PDF output (DG);
058     * 03-Feb-2004 : Fixed bug in getPreferredWidth() method (DG);
059     * 17-Feb-2004 : Added clone() method and fixed bug in equals() method (DG);
060     * 01-Apr-2004 : Changed java.awt.geom.Dimension2D to org.jfree.ui.Size2D 
061     *               because of JDK bug 4976448 which persists on JDK 1.3.1.  Also
062     *               fixed bug in getPreferredHeight() method (DG);
063     * 29-Apr-2004 : Fixed bug in getPreferredWidth() method - see bug id 
064     *               944173 (DG);
065     * 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0 
066     *               release (DG);
067     * 08-Feb-2005 : Updated for changes in RectangleConstraint class (DG);
068     * 11-Feb-2005 : Implemented PublicCloneable (DG);
069     * 20-Apr-2005 : Added support for tooltips (DG);
070     * 26-Apr-2005 : Removed LOGGER (DG);
071     * 06-Jun-2005 : Modified equals() to handle GradientPaint (DG);
072     * 06-Jul-2005 : Added flag to control whether or not the title expands to
073     *               fit the available space (DG);
074     * 07-Oct-2005 : Added textAlignment attribute (DG);
075     * 
076     */
077    
078    package org.jfree.chart.title;
079    
080    import java.awt.Color;
081    import java.awt.Font;
082    import java.awt.Graphics2D;
083    import java.awt.Paint;
084    import java.awt.geom.Rectangle2D;
085    import java.io.IOException;
086    import java.io.ObjectInputStream;
087    import java.io.ObjectOutputStream;
088    import java.io.Serializable;
089    
090    import org.jfree.chart.block.BlockResult;
091    import org.jfree.chart.block.EntityBlockParams;
092    import org.jfree.chart.block.LengthConstraintType;
093    import org.jfree.chart.block.RectangleConstraint;
094    import org.jfree.chart.entity.ChartEntity;
095    import org.jfree.chart.entity.EntityCollection;
096    import org.jfree.chart.entity.StandardEntityCollection;
097    import org.jfree.chart.event.TitleChangeEvent;
098    import org.jfree.data.Range;
099    import org.jfree.io.SerialUtilities;
100    import org.jfree.text.G2TextMeasurer;
101    import org.jfree.text.TextBlock;
102    import org.jfree.text.TextBlockAnchor;
103    import org.jfree.text.TextUtilities;
104    import org.jfree.ui.HorizontalAlignment;
105    import org.jfree.ui.RectangleEdge;
106    import org.jfree.ui.RectangleInsets;
107    import org.jfree.ui.Size2D;
108    import org.jfree.ui.VerticalAlignment;
109    import org.jfree.util.ObjectUtilities;
110    import org.jfree.util.PaintUtilities;
111    import org.jfree.util.PublicCloneable;
112    
113    /**
114     * A chart title that displays a text string with automatic wrapping as 
115     * required.
116     */
117    public class TextTitle extends Title 
118                           implements Serializable, Cloneable, PublicCloneable {
119    
120        /** For serialization. */
121        private static final long serialVersionUID = 8372008692127477443L;
122        
123        /** The default font. */
124        public static final Font DEFAULT_FONT 
125            = new Font("SansSerif", Font.BOLD, 12);
126    
127        /** The default text color. */
128        public static final Paint DEFAULT_TEXT_PAINT = Color.black;
129    
130        /** The title text. */
131        private String text;
132    
133        /** The font used to display the title. */
134        private Font font;
135        
136        /** The text alignment. */
137        private HorizontalAlignment textAlignment;
138    
139        /** The paint used to display the title text. */
140        private transient Paint paint;
141    
142        /** The background paint. */
143        private transient Paint backgroundPaint;
144    
145        /** The tool tip text (can be <code>null</code>). */
146        private String toolTipText;
147        
148        /** The URL text (can be <code>null</code>). */
149        private String urlText;
150        
151        /** The content. */
152        private TextBlock content;
153        
154        /** 
155         * A flag that controls whether the title expands to fit the available
156         * space..
157         */
158        private boolean expandToFitSpace = false;
159        
160        /**
161         * Creates a new title, using default attributes where necessary.
162         */
163        public TextTitle() {
164            this("");
165        }
166    
167        /**
168         * Creates a new title, using default attributes where necessary.
169         *
170         * @param text  the title text (<code>null</code> not permitted).
171         */
172        public TextTitle(String text) {
173            this(
174                text,
175                TextTitle.DEFAULT_FONT,
176                TextTitle.DEFAULT_TEXT_PAINT,
177                Title.DEFAULT_POSITION,
178                Title.DEFAULT_HORIZONTAL_ALIGNMENT,
179                Title.DEFAULT_VERTICAL_ALIGNMENT,
180                Title.DEFAULT_PADDING
181            );
182        }
183    
184        /**
185         * Creates a new title, using default attributes where necessary.
186         *
187         * @param text  the title text (<code>null</code> not permitted).
188         * @param font  the title font (<code>null</code> not permitted).
189         */
190        public TextTitle(String text, Font font) {
191    
192            this(
193                text, font,
194                TextTitle.DEFAULT_TEXT_PAINT,
195                Title.DEFAULT_POSITION,
196                Title.DEFAULT_HORIZONTAL_ALIGNMENT,
197                Title.DEFAULT_VERTICAL_ALIGNMENT,
198                Title.DEFAULT_PADDING
199            );
200    
201        }
202    
203        /**
204         * Creates a new title.
205         *
206         * @param text  the text for the title (<code>null</code> not permitted).
207         * @param font  the title font (<code>null</code> not permitted).
208         * @param paint  the title paint (<code>null</code> not permitted).
209         * @param position  the title position (<code>null</code> not permitted).
210         * @param horizontalAlignment  the horizontal alignment (<code>null</code> 
211         *                             not permitted).
212         * @param verticalAlignment  the vertical alignment (<code>null</code> not 
213         *                           permitted).
214         * @param padding  the space to leave around the outside of the title.
215         */
216        public TextTitle(String text, 
217                         Font font, 
218                         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        public String getText() {
255            return this.text;
256        }
257    
258        /**
259         * Sets the title to the specified text and sends a 
260         * {@link TitleChangeEvent} to all registered listeners.
261         *
262         * @param text  the text (<code>null</code> not permitted).
263         */
264        public void setText(String text) {
265            if (text == null) {
266                throw new NullPointerException("Null 'text' argument.");
267            }
268            if (!this.text.equals(text)) {
269                this.text = text;
270                notifyListeners(new TitleChangeEvent(this));
271            }
272        }
273    
274        /**
275         * Returns the text alignment.  This controls how the text is aligned 
276         * within the title's bounds, whereas the title's horizontal alignment
277         * controls how the title's bounding rectangle is aligned within the 
278         * drawing space.
279         * 
280         * @return The text alignment.
281         */
282        public HorizontalAlignment getTextAlignment() {
283            return this.textAlignment;
284        }
285        
286        /**
287         * Sets the text alignment.
288         * 
289         * @param alignment  the alignment (<code>null</code> not permitted).
290         */
291        public void setTextAlignment(HorizontalAlignment alignment) {
292            if (alignment == null) {
293                throw new IllegalArgumentException("Null 'alignment' argument.");
294            }
295            this.textAlignment = alignment;
296            notifyListeners(new TitleChangeEvent(this));
297        }
298        
299        /**
300         * Returns the font used to display the title string.
301         *
302         * @return The font (never <code>null</code>).
303         */
304        public Font getFont() {
305            return this.font;
306        }
307    
308        /**
309         * Sets the font used to display the title string.  Registered listeners 
310         * are notified that the title has been modified.
311         *
312         * @param font  the new font (<code>null</code> not permitted).
313         */
314        public void setFont(Font font) {
315            if (font == null) {
316                throw new IllegalArgumentException("Null 'font' argument.");
317            }
318            if (!this.font.equals(font)) {
319                this.font = font;
320                notifyListeners(new TitleChangeEvent(this));
321            }
322        }
323    
324        /**
325         * Returns the paint used to display the title string.
326         *
327         * @return The paint (never <code>null</code>).
328         */
329        public Paint getPaint() {
330            return this.paint;
331        }
332    
333        /**
334         * Sets the paint used to display the title string.  Registered listeners 
335         * are notified that the title has been modified.
336         *
337         * @param paint  the new paint (<code>null</code> not permitted).
338         */
339        public void setPaint(Paint paint) {
340            if (paint == null) {
341                throw new IllegalArgumentException("Null 'paint' argument.");
342            }
343            if (!this.paint.equals(paint)) {
344                this.paint = paint;
345                notifyListeners(new TitleChangeEvent(this));
346            }
347        }
348    
349        /**
350         * Returns the background paint.
351         *
352         * @return The paint (possibly <code>null</code>).
353         */
354        public Paint getBackgroundPaint() {
355            return this.backgroundPaint;
356        }
357    
358        /**
359         * Sets the background paint and sends a {@link TitleChangeEvent} to all 
360         * registered listeners.  If you set this attribute to <code>null</code>, 
361         * no background is painted (which makes the title background transparent).
362         *
363         * @param paint  the background paint (<code>null</code> permitted).
364         */
365        public void setBackgroundPaint(Paint paint) {
366            this.backgroundPaint = paint;
367            notifyListeners(new TitleChangeEvent(this));
368        }
369        
370        /**
371         * Returns the tool tip text.
372         *
373         * @return The tool tip text (possibly <code>null</code>).
374         */
375        public String getToolTipText() {
376            return this.toolTipText;
377        }
378    
379        /**
380         * Sets the tool tip text to the specified text and sends a 
381         * {@link TitleChangeEvent} to all registered listeners.
382         *
383         * @param text  the text (<code>null</code> permitted).
384         */
385        public void setToolTipText(String text) {
386            this.toolTipText = text;
387            notifyListeners(new TitleChangeEvent(this));
388        }
389    
390        /**
391         * Returns the URL text.
392         *
393         * @return The URL text (possibly <code>null</code>).
394         */
395        public String getURLText() {
396            return this.urlText;
397        }
398    
399        /**
400         * Sets the URL text to the specified text and sends a 
401         * {@link TitleChangeEvent} to all registered listeners.
402         *
403         * @param text  the text (<code>null</code> permitted).
404         */
405        public void setURLText(String text) {
406            this.urlText = text;
407            notifyListeners(new TitleChangeEvent(this));
408        }
409        
410        /**
411         * Returns the flag that controls whether or not the title expands to fit
412         * the available space.
413         * 
414         * @return The flag.
415         */
416        public boolean getExpandToFitSpace() {
417            return this.expandToFitSpace;   
418        }
419        
420        /**
421         * Sets the flag that controls whether the title expands to fit the 
422         * available space, and sends a {@link TitleChangeEvent} to all registered
423         * listeners.
424         * 
425         * @param expand  the flag.
426         */
427        public void setExpandToFitSpace(boolean expand) {
428            this.expandToFitSpace = expand;
429            notifyListeners(new TitleChangeEvent(this));        
430        }
431    
432        /**
433         * Arranges the contents of the block, within the given constraints, and 
434         * returns the block size.
435         * 
436         * @param g2  the graphics device.
437         * @param constraint  the constraint (<code>null</code> not permitted).
438         * 
439         * @return The block size (in Java2D units, never <code>null</code>).
440         */
441        public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) {
442            RectangleConstraint cc = toContentConstraint(constraint);
443            LengthConstraintType w = cc.getWidthConstraintType();
444            LengthConstraintType h = cc.getHeightConstraintType();
445            Size2D contentSize = null;
446            if (w == LengthConstraintType.NONE) {
447                if (h == LengthConstraintType.NONE) {
448                    throw new RuntimeException("Not yet implemented."); 
449                }
450                else if (h == LengthConstraintType.RANGE) {
451                    throw new RuntimeException("Not yet implemented."); 
452                }
453                else if (h == LengthConstraintType.FIXED) {
454                    throw new RuntimeException("Not yet implemented.");                 
455                }            
456            }
457            else if (w == LengthConstraintType.RANGE) {
458                if (h == LengthConstraintType.NONE) {
459                    throw new RuntimeException("Not yet implemented."); 
460                }
461                else if (h == LengthConstraintType.RANGE) {
462                    contentSize = arrangeRR(g2, cc.getWidthRange(), 
463                            cc.getHeightRange()); 
464                }
465                else if (h == LengthConstraintType.FIXED) {
466                    throw new RuntimeException("Not yet implemented.");                 
467                }
468            }
469            else if (w == LengthConstraintType.FIXED) {
470                if (h == LengthConstraintType.NONE) {
471                    throw new RuntimeException("Not yet implemented."); 
472                }
473                else if (h == LengthConstraintType.RANGE) {
474                    throw new RuntimeException("Not yet implemented."); 
475                }
476                else if (h == LengthConstraintType.FIXED) {
477                    throw new RuntimeException("Not yet implemented.");                 
478                }
479            }
480            return new Size2D(
481                calculateTotalWidth(contentSize.getWidth()),
482                calculateTotalHeight(contentSize.getHeight())
483            );
484        }
485        
486        /**
487         * Returns the content size for the title.
488         * 
489         * @param g2  the graphics device.
490         * @param widthRange  the width range.
491         * @param heightRange  the height range.
492         * 
493         * @return The content size.
494         */
495        protected Size2D arrangeRR(Graphics2D g2, Range widthRange, 
496                Range heightRange) {
497            float maxWidth = (float) widthRange.getUpperBound();
498            g2.setFont(this.font);
499            this.content = TextUtilities.createTextBlock(
500                this.text, this.font, this.paint, maxWidth, new G2TextMeasurer(g2));
501            this.content.setLineAlignment(this.textAlignment);
502            Size2D contentSize = this.content.calculateDimensions(g2);
503            if (this.expandToFitSpace) {
504                return new Size2D(maxWidth, contentSize.getHeight());
505            }
506            else {
507                return contentSize;
508            }
509        }
510        
511        /**
512         * Draws the title on a Java 2D graphics device (such as the screen or a 
513         * printer).
514         *
515         * @param g2  the graphics device.
516         * @param area  the area allocated for the title.
517         */
518        public void draw(Graphics2D g2, Rectangle2D area) {
519            draw(g2, area, null);
520        }
521        
522        /**
523         * Draws the block within the specified area.
524         * 
525         * @param g2  the graphics device.
526         * @param area  the area.
527         * @param params  if this is an instance of {@link EntityBlockParams} it
528         *                is used to determine whether or not an 
529         *                {@link EntityCollection} is returned by this method.
530         * 
531         * @return An {@link EntityCollection} containing a chart entity for the
532         *         title, or <code>null</code>.
533         */
534        public Object draw(Graphics2D g2, Rectangle2D area, Object params) {
535            if (this.content == null) {
536                return null;   
537            }
538            area = trimMargin(area);
539            drawBorder(g2, area);
540            if (this.text.equals("")) {
541                return null;
542            }
543            ChartEntity entity = null;
544            if (params instanceof EntityBlockParams) {
545                EntityBlockParams p = (EntityBlockParams) params;
546                if (p.getGenerateEntities()) {
547                    entity = new ChartEntity(area, this.toolTipText, this.urlText);    
548                }
549            }
550            area = trimBorder(area);
551            if (this.backgroundPaint != null) {
552                g2.setPaint(this.backgroundPaint);
553                g2.fill(area);
554            }
555            area = trimPadding(area);
556            RectangleEdge position = getPosition();
557            if (position == RectangleEdge.TOP || position == RectangleEdge.BOTTOM) {
558                drawHorizontal(g2, area);
559            }
560            else if (position == RectangleEdge.LEFT 
561                     || position == RectangleEdge.RIGHT) {
562                drawVertical(g2, area);
563            }
564            BlockResult result = new BlockResult();
565            if (entity != null) {
566                StandardEntityCollection sec = new StandardEntityCollection();
567                sec.add(entity);
568                result.setEntityCollection(sec);
569            }
570            return result;
571        }
572    
573        /**
574         * Draws a the title horizontally within the specified area.  This method 
575         * will be called from the {@link #draw(Graphics2D, Rectangle2D) draw} 
576         * method.
577         * 
578         * @param g2  the graphics device.
579         * @param area  the area for the title.
580         */
581        protected void drawHorizontal(Graphics2D g2, Rectangle2D area) {
582            Rectangle2D titleArea = (Rectangle2D) area.clone();
583            g2.setFont(this.font);
584            g2.setPaint(this.paint);
585            TextBlockAnchor anchor = null;
586            float x = 0.0f;
587            HorizontalAlignment horizontalAlignment = getHorizontalAlignment();
588            if (horizontalAlignment == HorizontalAlignment.LEFT) {
589                x = (float) titleArea.getX();
590                anchor = TextBlockAnchor.TOP_LEFT;
591            }
592            else if (horizontalAlignment == HorizontalAlignment.RIGHT) {
593                x = (float) titleArea.getMaxX();
594                anchor = TextBlockAnchor.TOP_RIGHT;
595            }
596            else if (horizontalAlignment == HorizontalAlignment.CENTER) {
597                x = (float) titleArea.getCenterX();
598                anchor = TextBlockAnchor.TOP_CENTER;
599            }
600            float y = 0.0f;
601            RectangleEdge position = getPosition();
602            if (position == RectangleEdge.TOP) {
603                y = (float) titleArea.getY();
604            }
605            else if (position == RectangleEdge.BOTTOM) {
606                y = (float) titleArea.getMaxY();
607                if (horizontalAlignment == HorizontalAlignment.LEFT) {
608                    anchor = TextBlockAnchor.BOTTOM_LEFT;
609                }
610                else if (horizontalAlignment == HorizontalAlignment.CENTER) {
611                    anchor = TextBlockAnchor.BOTTOM_CENTER;
612                }
613                else if (horizontalAlignment == HorizontalAlignment.RIGHT) {
614                    anchor = TextBlockAnchor.BOTTOM_RIGHT;
615                }
616            }
617            this.content.draw(g2, x, y, anchor);
618        }
619        
620        /**
621         * Draws a the title vertically within the specified area.  This method 
622         * will be called from the {@link #draw(Graphics2D, Rectangle2D) draw} 
623         * method.
624         * 
625         * @param g2  the graphics device.
626         * @param area  the area for the title.
627         */
628        protected void drawVertical(Graphics2D g2, Rectangle2D area) {
629            Rectangle2D titleArea = (Rectangle2D) area.clone();
630            g2.setFont(this.font);
631            g2.setPaint(this.paint);
632            TextBlockAnchor anchor = null;
633            float y = 0.0f;
634            VerticalAlignment verticalAlignment = getVerticalAlignment();
635            if (verticalAlignment == VerticalAlignment.TOP) {
636                y = (float) titleArea.getY();
637                anchor = TextBlockAnchor.TOP_RIGHT;
638            }
639            else if (verticalAlignment == VerticalAlignment.BOTTOM) {
640                y = (float) titleArea.getMaxY();
641                anchor = TextBlockAnchor.TOP_LEFT;
642            }
643            else if (verticalAlignment == VerticalAlignment.CENTER) {
644                y = (float) titleArea.getCenterY();
645                anchor = TextBlockAnchor.TOP_CENTER;
646            }
647            float x = 0.0f;
648            RectangleEdge position = getPosition();
649            if (position == RectangleEdge.LEFT) {
650                x = (float) titleArea.getX();
651            }
652            else if (position == RectangleEdge.RIGHT) {
653                x = (float) titleArea.getMaxX();
654                if (verticalAlignment == VerticalAlignment.TOP) {
655                    anchor = TextBlockAnchor.BOTTOM_RIGHT;
656                }
657                else if (verticalAlignment == VerticalAlignment.CENTER) {
658                    anchor = TextBlockAnchor.BOTTOM_CENTER;
659                }
660                else if (verticalAlignment == VerticalAlignment.BOTTOM) {
661                    anchor = TextBlockAnchor.BOTTOM_LEFT;
662                }
663            }
664            this.content.draw(g2, x, y, anchor, x, y, -Math.PI / 2.0);
665        }
666    
667        /**
668         * Tests this title for equality with another object.
669         *
670         * @param obj  the object (<code>null</code> permitted).
671         *
672         * @return <code>true</code> or <code>false</code>.
673         */
674        public boolean equals(Object obj) {
675            if (obj == this) {
676                return true;
677            }
678            if (!(obj instanceof TextTitle)) {
679                return false;
680            }
681            if (!super.equals(obj)) {
682                return false;
683            }
684            TextTitle that = (TextTitle) obj;
685            if (!ObjectUtilities.equal(this.text, that.text)) {
686                return false;
687            }
688            if (!ObjectUtilities.equal(this.font, that.font)) {
689                return false;
690            }
691            if (!PaintUtilities.equal(this.paint, that.paint)) {
692                return false;
693            }
694            if (this.textAlignment != that.textAlignment) {
695                return false;
696            }
697            if (!PaintUtilities.equal(this.backgroundPaint, that.backgroundPaint)) {
698                return false;
699            }
700            return true;
701        }
702    
703        /**
704         * Returns a hash code.
705         * 
706         * @return A hash code.
707         */
708        public int hashCode() {
709            int result = super.hashCode();
710            result = 29 * result + (this.text != null ? this.text.hashCode() : 0);
711            result = 29 * result + (this.font != null ? this.font.hashCode() : 0);
712            result = 29 * result + (this.paint != null ? this.paint.hashCode() : 0);
713            result = 29 * result + (
714                this.backgroundPaint != null ? this.backgroundPaint.hashCode() : 0
715            );
716            return result;
717        }
718    
719        /**
720         * Returns a clone of this object.
721         * 
722         * @return A clone.
723         * 
724         * @throws CloneNotSupportedException never.
725         */
726        public Object clone() throws CloneNotSupportedException {
727            return super.clone();
728        }
729        
730        /**
731         * Provides serialization support.
732         *
733         * @param stream  the output stream.
734         *
735         * @throws IOException  if there is an I/O error.
736         */
737        private void writeObject(ObjectOutputStream stream) throws IOException {
738            stream.defaultWriteObject();
739            SerialUtilities.writePaint(this.paint, stream);
740            SerialUtilities.writePaint(this.backgroundPaint, stream);
741        }
742    
743        /**
744         * Provides serialization support.
745         *
746         * @param stream  the input stream.
747         *
748         * @throws IOException  if there is an I/O error.
749         * @throws ClassNotFoundException  if there is a classpath problem.
750         */
751        private void readObject(ObjectInputStream stream) 
752            throws IOException, ClassNotFoundException 
753        {
754            stream.defaultReadObject();
755            this.paint = SerialUtilities.readPaint(stream);
756            this.backgroundPaint = SerialUtilities.readPaint(stream);
757        }
758    
759    }
760