001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2009, by Object Refinery Limited and Contributors.
006     *
007     * Project Info:  http://www.jfree.org/jfreechart/index.html
008     *
009     * This library is free software; you can redistribute it and/or modify it
010     * under the terms of the GNU Lesser General Public License as published by
011     * the Free Software Foundation; either version 2.1 of the License, or
012     * (at your option) any later version.
013     *
014     * This library is distributed in the hope that it will be useful, but
015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017     * License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this library; if not, write to the Free Software
021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
022     * USA.
023     *
024     * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025     * in the United States and other countries.]
026     *
027     * ---------------------
028     * PaintScaleLegend.java
029     * ---------------------
030     * (C) Copyright 2007-2009, by Object Refinery Limited.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   Peter Kolb - see patch 2686872;
034     *
035     * Changes
036     * -------
037     * 22-Jan-2007 : Version 1 (DG);
038     * 18-Jun-2008 : Fixed bug drawing scale with log axis (DG);
039     * 16-Apr-2009 : Patch 2686872 implementing AxisChangeListener, and fix for
040     *               ignored stripOutlineVisible flag (DG);
041     *
042     */
043    
044    package org.jfree.chart.title;
045    
046    import java.awt.BasicStroke;
047    import java.awt.Color;
048    import java.awt.Graphics2D;
049    import java.awt.Paint;
050    import java.awt.Stroke;
051    import java.awt.geom.Rectangle2D;
052    import java.io.IOException;
053    import java.io.ObjectInputStream;
054    import java.io.ObjectOutputStream;
055    
056    import org.jfree.chart.axis.AxisLocation;
057    import org.jfree.chart.axis.AxisSpace;
058    import org.jfree.chart.axis.ValueAxis;
059    import org.jfree.chart.block.LengthConstraintType;
060    import org.jfree.chart.block.RectangleConstraint;
061    import org.jfree.chart.event.AxisChangeEvent;
062    import org.jfree.chart.event.AxisChangeListener;
063    import org.jfree.chart.event.TitleChangeEvent;
064    import org.jfree.chart.plot.Plot;
065    import org.jfree.chart.plot.PlotOrientation;
066    import org.jfree.chart.renderer.PaintScale;
067    import org.jfree.data.Range;
068    import org.jfree.io.SerialUtilities;
069    import org.jfree.ui.RectangleEdge;
070    import org.jfree.ui.Size2D;
071    import org.jfree.util.PaintUtilities;
072    import org.jfree.util.PublicCloneable;
073    
074    /**
075     * A legend that shows a range of values and their associated colors, driven
076     * by an underlying {@link PaintScale} implementation.
077     *
078     * @since 1.0.4
079     */
080    public class PaintScaleLegend extends Title implements AxisChangeListener,
081            PublicCloneable {
082    
083        /** For serialization. */
084        static final long serialVersionUID = -1365146490993227503L;
085    
086        /** The paint scale (never <code>null</code>). */
087        private PaintScale scale;
088    
089        /** The value axis (never <code>null</code>). */
090        private ValueAxis axis;
091    
092        /**
093         * The axis location (handles both orientations, never
094         * <code>null</code>).
095         */
096        private AxisLocation axisLocation;
097    
098        /** The offset between the axis and the paint strip (in Java2D units). */
099        private double axisOffset;
100    
101        /** The thickness of the paint strip (in Java2D units). */
102        private double stripWidth;
103    
104        /**
105         * A flag that controls whether or not an outline is drawn around the
106         * paint strip.
107         */
108        private boolean stripOutlineVisible;
109    
110        /** The paint used to draw an outline around the paint strip. */
111        private transient Paint stripOutlinePaint;
112    
113        /** The stroke used to draw an outline around the paint strip. */
114        private transient Stroke stripOutlineStroke;
115    
116        /** The background paint (never <code>null</code>). */
117        private transient Paint backgroundPaint;
118    
119        /**
120         * The number of subdivisions for the scale when rendering.
121         *
122         * @since 1.0.11
123         */
124        private int subdivisions;
125    
126        /**
127         * Creates a new instance.
128         *
129         * @param scale  the scale (<code>null</code> not permitted).
130         * @param axis  the axis (<code>null</code> not permitted).
131         */
132        public PaintScaleLegend(PaintScale scale, ValueAxis axis) {
133            if (axis == null) {
134                throw new IllegalArgumentException("Null 'axis' argument.");
135            }
136            this.scale = scale;
137            this.axis = axis;
138            this.axis.addChangeListener(this);
139            this.axisLocation = AxisLocation.BOTTOM_OR_LEFT;
140            this.axisOffset = 0.0;
141            this.axis.setRange(scale.getLowerBound(), scale.getUpperBound());
142            this.stripWidth = 15.0;
143            this.stripOutlineVisible = true;
144            this.stripOutlinePaint = Color.gray;
145            this.stripOutlineStroke = new BasicStroke(0.5f);
146            this.backgroundPaint = Color.white;
147            this.subdivisions = 100;
148        }
149    
150        /**
151         * Returns the scale used to convert values to colors.
152         *
153         * @return The scale (never <code>null</code>).
154         *
155         * @see #setScale(PaintScale)
156         */
157        public PaintScale getScale() {
158            return this.scale;
159        }
160    
161        /**
162         * Sets the scale and sends a {@link TitleChangeEvent} to all registered
163         * listeners.
164         *
165         * @param scale  the scale (<code>null</code> not permitted).
166         *
167         * @see #getScale()
168         */
169        public void setScale(PaintScale scale) {
170            if (scale == null) {
171                throw new IllegalArgumentException("Null 'scale' argument.");
172            }
173            this.scale = scale;
174            notifyListeners(new TitleChangeEvent(this));
175        }
176    
177        /**
178         * Returns the axis for the paint scale.
179         *
180         * @return The axis (never <code>null</code>).
181         *
182         * @see #setAxis(ValueAxis)
183         */
184        public ValueAxis getAxis() {
185            return this.axis;
186        }
187    
188        /**
189         * Sets the axis for the paint scale and sends a {@link TitleChangeEvent}
190         * to all registered listeners.
191         *
192         * @param axis  the axis (<code>null</code> not permitted).
193         *
194         * @see #getAxis()
195         */
196        public void setAxis(ValueAxis axis) {
197            if (axis == null) {
198                throw new IllegalArgumentException("Null 'axis' argument.");
199            }
200            this.axis.removeChangeListener(this);
201            this.axis = axis;
202            this.axis.addChangeListener(this);
203            notifyListeners(new TitleChangeEvent(this));
204        }
205    
206        /**
207         * Returns the axis location.
208         *
209         * @return The axis location (never <code>null</code>).
210         *
211         * @see #setAxisLocation(AxisLocation)
212         */
213        public AxisLocation getAxisLocation() {
214            return this.axisLocation;
215        }
216    
217        /**
218         * Sets the axis location and sends a {@link TitleChangeEvent} to all
219         * registered listeners.
220         *
221         * @param location  the location (<code>null</code> not permitted).
222         *
223         * @see #getAxisLocation()
224         */
225        public void setAxisLocation(AxisLocation location) {
226            if (location == null) {
227                throw new IllegalArgumentException("Null 'location' argument.");
228            }
229            this.axisLocation = location;
230            notifyListeners(new TitleChangeEvent(this));
231        }
232    
233        /**
234         * Returns the offset between the axis and the paint strip.
235         *
236         * @return The offset between the axis and the paint strip.
237         *
238         * @see #setAxisOffset(double)
239         */
240        public double getAxisOffset() {
241            return this.axisOffset;
242        }
243    
244        /**
245         * Sets the offset between the axis and the paint strip and sends a
246         * {@link TitleChangeEvent} to all registered listeners.
247         *
248         * @param offset  the offset.
249         */
250        public void setAxisOffset(double offset) {
251            this.axisOffset = offset;
252            notifyListeners(new TitleChangeEvent(this));
253        }
254    
255        /**
256         * Returns the width of the paint strip, in Java2D units.
257         *
258         * @return The width of the paint strip.
259         *
260         * @see #setStripWidth(double)
261         */
262        public double getStripWidth() {
263            return this.stripWidth;
264        }
265    
266        /**
267         * Sets the width of the paint strip and sends a {@link TitleChangeEvent}
268         * to all registered listeners.
269         *
270         * @param width  the width.
271         *
272         * @see #getStripWidth()
273         */
274        public void setStripWidth(double width) {
275            this.stripWidth = width;
276            notifyListeners(new TitleChangeEvent(this));
277        }
278    
279        /**
280         * Returns the flag that controls whether or not an outline is drawn
281         * around the paint strip.
282         *
283         * @return A boolean.
284         *
285         * @see #setStripOutlineVisible(boolean)
286         */
287        public boolean isStripOutlineVisible() {
288            return this.stripOutlineVisible;
289        }
290    
291        /**
292         * Sets the flag that controls whether or not an outline is drawn around
293         * the paint strip, and sends a {@link TitleChangeEvent} to all registered
294         * listeners.
295         *
296         * @param visible  the flag.
297         *
298         * @see #isStripOutlineVisible()
299         */
300        public void setStripOutlineVisible(boolean visible) {
301            this.stripOutlineVisible = visible;
302            notifyListeners(new TitleChangeEvent(this));
303        }
304    
305        /**
306         * Returns the paint used to draw the outline of the paint strip.
307         *
308         * @return The paint (never <code>null</code>).
309         *
310         * @see #setStripOutlinePaint(Paint)
311         */
312        public Paint getStripOutlinePaint() {
313            return this.stripOutlinePaint;
314        }
315    
316        /**
317         * Sets the paint used to draw the outline of the paint strip, and sends
318         * a {@link TitleChangeEvent} to all registered listeners.
319         *
320         * @param paint  the paint (<code>null</code> not permitted).
321         *
322         * @see #getStripOutlinePaint()
323         */
324        public void setStripOutlinePaint(Paint paint) {
325            if (paint == null) {
326                throw new IllegalArgumentException("Null 'paint' argument.");
327            }
328            this.stripOutlinePaint = paint;
329            notifyListeners(new TitleChangeEvent(this));
330        }
331    
332        /**
333         * Returns the stroke used to draw the outline around the paint strip.
334         *
335         * @return The stroke (never <code>null</code>).
336         *
337         * @see #setStripOutlineStroke(Stroke)
338         */
339        public Stroke getStripOutlineStroke() {
340            return this.stripOutlineStroke;
341        }
342    
343        /**
344         * Sets the stroke used to draw the outline around the paint strip and
345         * sends a {@link TitleChangeEvent} to all registered listeners.
346         *
347         * @param stroke  the stroke (<code>null</code> not permitted).
348         *
349         * @see #getStripOutlineStroke()
350         */
351        public void setStripOutlineStroke(Stroke stroke) {
352            if (stroke == null) {
353                throw new IllegalArgumentException("Null 'stroke' argument.");
354            }
355            this.stripOutlineStroke = stroke;
356            notifyListeners(new TitleChangeEvent(this));
357        }
358    
359        /**
360         * Returns the background paint.
361         *
362         * @return The background paint.
363         */
364        public Paint getBackgroundPaint() {
365            return this.backgroundPaint;
366        }
367    
368        /**
369         * Sets the background paint and sends a {@link TitleChangeEvent} to all
370         * registered listeners.
371         *
372         * @param paint  the paint (<code>null</code> permitted).
373         */
374        public void setBackgroundPaint(Paint paint) {
375            this.backgroundPaint = paint;
376            notifyListeners(new TitleChangeEvent(this));
377        }
378    
379        /**
380         * Returns the number of subdivisions used to draw the scale.
381         *
382         * @return The subdivision count.
383         *
384         * @since 1.0.11
385         */
386        public int getSubdivisionCount() {
387            return this.subdivisions;
388        }
389    
390        /**
391         * Sets the subdivision count and sends a {@link TitleChangeEvent} to
392         * all registered listeners.
393         *
394         * @param count  the count.
395         *
396         * @since 1.0.11
397         */
398        public void setSubdivisionCount(int count) {
399            if (count <= 0) {
400                throw new IllegalArgumentException("Requires 'count' > 0.");
401            }
402            this.subdivisions = count;
403            notifyListeners(new TitleChangeEvent(this));
404        }
405    
406        /**
407         * Receives notification of an axis change event and responds by firing
408         * a title change event.
409         *
410         * @param event  the event.
411         *
412         * @since 1.0.13
413         */
414        public void axisChanged(AxisChangeEvent event) {
415            if (this.axis == event.getAxis()) {
416                notifyListeners(new TitleChangeEvent(this));
417            }
418        }
419    
420        /**
421         * Arranges the contents of the block, within the given constraints, and
422         * returns the block size.
423         *
424         * @param g2  the graphics device.
425         * @param constraint  the constraint (<code>null</code> not permitted).
426         *
427         * @return The block size (in Java2D units, never <code>null</code>).
428         */
429        public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) {
430            RectangleConstraint cc = toContentConstraint(constraint);
431            LengthConstraintType w = cc.getWidthConstraintType();
432            LengthConstraintType h = cc.getHeightConstraintType();
433            Size2D contentSize = null;
434            if (w == LengthConstraintType.NONE) {
435                if (h == LengthConstraintType.NONE) {
436                    contentSize = new Size2D(getWidth(), getHeight());
437                }
438                else if (h == LengthConstraintType.RANGE) {
439                    throw new RuntimeException("Not yet implemented.");
440                }
441                else if (h == LengthConstraintType.FIXED) {
442                    throw new RuntimeException("Not yet implemented.");
443                }
444            }
445            else if (w == LengthConstraintType.RANGE) {
446                if (h == LengthConstraintType.NONE) {
447                    throw new RuntimeException("Not yet implemented.");
448                }
449                else if (h == LengthConstraintType.RANGE) {
450                    contentSize = arrangeRR(g2, cc.getWidthRange(),
451                            cc.getHeightRange());
452                }
453                else if (h == LengthConstraintType.FIXED) {
454                    throw new RuntimeException("Not yet implemented.");
455                }
456            }
457            else if (w == LengthConstraintType.FIXED) {
458                if (h == LengthConstraintType.NONE) {
459                    throw new RuntimeException("Not yet implemented.");
460                }
461                else if (h == LengthConstraintType.RANGE) {
462                    throw new RuntimeException("Not yet implemented.");
463                }
464                else if (h == LengthConstraintType.FIXED) {
465                    throw new RuntimeException("Not yet implemented.");
466                }
467            }
468            return new Size2D(calculateTotalWidth(contentSize.getWidth()),
469                    calculateTotalHeight(contentSize.getHeight()));
470        }
471    
472        /**
473         * Returns the content size for the title.  This will reflect the fact that
474         * a text title positioned on the left or right of a chart will be rotated
475         * 90 degrees.
476         *
477         * @param g2  the graphics device.
478         * @param widthRange  the width range.
479         * @param heightRange  the height range.
480         *
481         * @return The content size.
482         */
483        protected Size2D arrangeRR(Graphics2D g2, Range widthRange,
484                Range heightRange) {
485    
486            RectangleEdge position = getPosition();
487            if (position == RectangleEdge.TOP || position == RectangleEdge.BOTTOM) {
488    
489    
490                float maxWidth = (float) widthRange.getUpperBound();
491    
492                // determine the space required for the axis
493                AxisSpace space = this.axis.reserveSpace(g2, null,
494                        new Rectangle2D.Double(0, 0, maxWidth, 100),
495                        RectangleEdge.BOTTOM, null);
496    
497                return new Size2D(maxWidth, this.stripWidth + this.axisOffset
498                        + space.getTop() + space.getBottom());
499            }
500            else if (position == RectangleEdge.LEFT || position
501                    == RectangleEdge.RIGHT) {
502                float maxHeight = (float) heightRange.getUpperBound();
503                AxisSpace space = this.axis.reserveSpace(g2, null,
504                        new Rectangle2D.Double(0, 0, 100, maxHeight),
505                        RectangleEdge.RIGHT, null);
506                return new Size2D(this.stripWidth + this.axisOffset
507                        + space.getLeft() + space.getRight(), maxHeight);
508            }
509            else {
510                throw new RuntimeException("Unrecognised position.");
511            }
512        }
513    
514        /**
515         * Draws the legend within the specified area.
516         *
517         * @param g2  the graphics target (<code>null</code> not permitted).
518         * @param area  the drawing area (<code>null</code> not permitted).
519         */
520        public void draw(Graphics2D g2, Rectangle2D area) {
521            draw(g2, area, null);
522        }
523    
524        /**
525         * Draws the legend within the specified area.
526         *
527         * @param g2  the graphics target (<code>null</code> not permitted).
528         * @param area  the drawing area (<code>null</code> not permitted).
529         * @param params  drawing parameters (ignored here).
530         *
531         * @return <code>null</code>.
532         */
533        public Object draw(Graphics2D g2, Rectangle2D area, Object params) {
534    
535            Rectangle2D target = (Rectangle2D) area.clone();
536            target = trimMargin(target);
537            if (this.backgroundPaint != null) {
538                g2.setPaint(this.backgroundPaint);
539                g2.fill(target);
540            }
541            getFrame().draw(g2, target);
542            getFrame().getInsets().trim(target);
543            target = trimPadding(target);
544            double base = this.axis.getLowerBound();
545            double increment = this.axis.getRange().getLength() / this.subdivisions;
546            Rectangle2D r = new Rectangle2D.Double();
547    
548            if (RectangleEdge.isTopOrBottom(getPosition())) {
549                RectangleEdge axisEdge = Plot.resolveRangeAxisLocation(
550                        this.axisLocation, PlotOrientation.HORIZONTAL);
551                if (axisEdge == RectangleEdge.TOP) {
552                    for (int i = 0; i < this.subdivisions; i++) {
553                        double v = base + (i * increment);
554                        Paint p = this.scale.getPaint(v);
555                        double vv0 = this.axis.valueToJava2D(v, target,
556                                RectangleEdge.TOP);
557                        double vv1 = this.axis.valueToJava2D(v + increment, target,
558                                RectangleEdge.TOP);
559                        double ww = Math.abs(vv1 - vv0) + 1.0;
560                        r.setRect(Math.min(vv0, vv1), target.getMaxY()
561                                - this.stripWidth, ww, this.stripWidth);
562                        g2.setPaint(p);
563                        g2.fill(r);
564                    }
565                    if (isStripOutlineVisible()) {
566                        g2.setPaint(this.stripOutlinePaint);
567                        g2.setStroke(this.stripOutlineStroke);
568                        g2.draw(new Rectangle2D.Double(target.getMinX(),
569                                target.getMaxY() - this.stripWidth,
570                                target.getWidth(), this.stripWidth));
571                    }
572                    this.axis.draw(g2, target.getMaxY() - this.stripWidth
573                            - this.axisOffset, target, target, RectangleEdge.TOP,
574                            null);
575                }
576                else if (axisEdge == RectangleEdge.BOTTOM) {
577                    for (int i = 0; i < this.subdivisions; i++) {
578                        double v = base + (i * increment);
579                        Paint p = this.scale.getPaint(v);
580                        double vv0 = this.axis.valueToJava2D(v, target,
581                                RectangleEdge.BOTTOM);
582                        double vv1 = this.axis.valueToJava2D(v + increment, target,
583                                RectangleEdge.BOTTOM);
584                        double ww = Math.abs(vv1 - vv0) + 1.0;
585                        r.setRect(Math.min(vv0, vv1), target.getMinY(), ww,
586                                this.stripWidth);
587                        g2.setPaint(p);
588                        g2.fill(r);
589                    }
590                    if (isStripOutlineVisible()) {
591                        g2.setPaint(this.stripOutlinePaint);
592                        g2.setStroke(this.stripOutlineStroke);
593                        g2.draw(new Rectangle2D.Double(target.getMinX(),
594                                target.getMinY(), target.getWidth(),
595                                this.stripWidth));
596                    }
597                    this.axis.draw(g2, target.getMinY() + this.stripWidth
598                            + this.axisOffset, target, target,
599                            RectangleEdge.BOTTOM, null);
600                }
601            }
602            else {
603                RectangleEdge axisEdge = Plot.resolveRangeAxisLocation(
604                        this.axisLocation, PlotOrientation.VERTICAL);
605                if (axisEdge == RectangleEdge.LEFT) {
606                    for (int i = 0; i < this.subdivisions; i++) {
607                        double v = base + (i * increment);
608                        Paint p = this.scale.getPaint(v);
609                        double vv0 = this.axis.valueToJava2D(v, target,
610                                RectangleEdge.LEFT);
611                        double vv1 = this.axis.valueToJava2D(v + increment, target,
612                                RectangleEdge.LEFT);
613                        double hh = Math.abs(vv1 - vv0) + 1.0;
614                        r.setRect(target.getMaxX() - this.stripWidth,
615                                Math.min(vv0, vv1), this.stripWidth, hh);
616                        g2.setPaint(p);
617                        g2.fill(r);
618                    }
619                    if (isStripOutlineVisible()) {
620                        g2.setPaint(this.stripOutlinePaint);
621                        g2.setStroke(this.stripOutlineStroke);
622                        g2.draw(new Rectangle2D.Double(target.getMaxX()
623                                - this.stripWidth, target.getMinY(), this.stripWidth,
624                                target.getHeight()));
625                    }
626                    this.axis.draw(g2, target.getMaxX() - this.stripWidth
627                            - this.axisOffset, target, target, RectangleEdge.LEFT,
628                            null);
629                }
630                else if (axisEdge == RectangleEdge.RIGHT) {
631                    for (int i = 0; i < this.subdivisions; i++) {
632                        double v = base + (i * increment);
633                        Paint p = this.scale.getPaint(v);
634                        double vv0 = this.axis.valueToJava2D(v, target,
635                                RectangleEdge.LEFT);
636                        double vv1 = this.axis.valueToJava2D(v + increment, target,
637                                RectangleEdge.LEFT);
638                        double hh = Math.abs(vv1 - vv0) + 1.0;
639                        r.setRect(target.getMinX(), Math.min(vv0, vv1),
640                                this.stripWidth, hh);
641                        g2.setPaint(p);
642                        g2.fill(r);
643                    }
644                    if (isStripOutlineVisible()) {
645                        g2.setPaint(this.stripOutlinePaint);
646                        g2.setStroke(this.stripOutlineStroke);
647                        g2.draw(new Rectangle2D.Double(target.getMinX(),
648                                target.getMinY(), this.stripWidth,
649                                target.getHeight()));
650                    }
651                    this.axis.draw(g2, target.getMinX() + this.stripWidth
652                            + this.axisOffset, target, target, RectangleEdge.RIGHT,
653                            null);
654                }
655            }
656            return null;
657        }
658    
659        /**
660         * Tests this legend for equality with an arbitrary object.
661         *
662         * @param obj  the object (<code>null</code> permitted).
663         *
664         * @return A boolean.
665         */
666        public boolean equals(Object obj) {
667            if (!(obj instanceof PaintScaleLegend)) {
668                return false;
669            }
670            PaintScaleLegend that = (PaintScaleLegend) obj;
671            if (!this.scale.equals(that.scale)) {
672                return false;
673            }
674            if (!this.axis.equals(that.axis)) {
675                return false;
676            }
677            if (!this.axisLocation.equals(that.axisLocation)) {
678                return false;
679            }
680            if (this.axisOffset != that.axisOffset) {
681                return false;
682            }
683            if (this.stripWidth != that.stripWidth) {
684                return false;
685            }
686            if (this.stripOutlineVisible != that.stripOutlineVisible) {
687                return false;
688            }
689            if (!PaintUtilities.equal(this.stripOutlinePaint,
690                    that.stripOutlinePaint)) {
691                return false;
692            }
693            if (!this.stripOutlineStroke.equals(that.stripOutlineStroke)) {
694                return false;
695            }
696            if (!PaintUtilities.equal(this.backgroundPaint, that.backgroundPaint)) {
697                return false;
698            }
699            if (this.subdivisions != that.subdivisions) {
700                return false;
701            }
702            return super.equals(obj);
703        }
704    
705        /**
706         * Provides serialization support.
707         *
708         * @param stream  the output stream.
709         *
710         * @throws IOException  if there is an I/O error.
711         */
712        private void writeObject(ObjectOutputStream stream) throws IOException {
713            stream.defaultWriteObject();
714            SerialUtilities.writePaint(this.backgroundPaint, stream);
715            SerialUtilities.writePaint(this.stripOutlinePaint, stream);
716            SerialUtilities.writeStroke(this.stripOutlineStroke, stream);
717        }
718    
719        /**
720         * Provides serialization support.
721         *
722         * @param stream  the input stream.
723         *
724         * @throws IOException  if there is an I/O error.
725         * @throws ClassNotFoundException  if there is a classpath problem.
726         */
727        private void readObject(ObjectInputStream stream)
728                throws IOException, ClassNotFoundException {
729            stream.defaultReadObject();
730            this.backgroundPaint = SerialUtilities.readPaint(stream);
731            this.stripOutlinePaint = SerialUtilities.readPaint(stream);
732            this.stripOutlineStroke = SerialUtilities.readStroke(stream);
733        }
734    
735    }