001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2021, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * ------------
028 * DialCap.java
029 * ------------
030 * (C) Copyright 2006-2021, by Object Refinery Limited.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   -;
034 *
035 */
036
037package org.jfree.chart.plot.dial;
038
039import java.awt.BasicStroke;
040import java.awt.Color;
041import java.awt.Graphics2D;
042import java.awt.Paint;
043import java.awt.Stroke;
044import java.awt.geom.Ellipse2D;
045import java.awt.geom.Rectangle2D;
046import java.io.IOException;
047import java.io.ObjectInputStream;
048import java.io.ObjectOutputStream;
049import java.io.Serializable;
050
051import org.jfree.chart.HashUtils;
052import org.jfree.chart.util.PaintUtils;
053import org.jfree.chart.util.Args;
054import org.jfree.chart.util.PublicCloneable;
055import org.jfree.chart.util.SerialUtils;
056
057/**
058 * A regular dial layer that can be used to draw a cap over the center of
059 * the dial (the base of the dial pointer(s)).
060 */
061public class DialCap extends AbstractDialLayer implements DialLayer, Cloneable,
062        PublicCloneable, Serializable {
063
064    /** For serialization. */
065    static final long serialVersionUID = -2929484264982524463L;
066
067    /**
068     * The radius of the cap, as a percentage of the framing rectangle.
069     */
070    private double radius;
071
072    /**
073     * The fill paint.  This field is transient because it requires special
074     * handling for serialization.
075     */
076    private transient Paint fillPaint;
077
078    /**
079     * The paint used to draw the cap outline (this should never be
080     * {@code null}).  This field is transient because it requires
081     * special handling for serialization.
082     */
083    private transient Paint outlinePaint;
084
085    /**
086     * The stroke used to draw the cap outline (this should never be
087     * {@code null}).   This field is transient because it requires
088     * special handling for serialization.
089     */
090    private transient Stroke outlineStroke;
091
092    /**
093     * Creates a new instance of {@code StandardDialBackground}.  The
094     * default background paint is {@code Color.WHITE}.
095     */
096    public DialCap() {
097        this.radius = 0.05;
098        this.fillPaint = Color.WHITE;
099        this.outlinePaint = Color.BLACK;
100        this.outlineStroke = new BasicStroke(2.0f);
101    }
102
103    /**
104     * Returns the radius of the cap, as a percentage of the dial's framing
105     * rectangle.
106     *
107     * @return The radius.
108     *
109     * @see #setRadius(double)
110     */
111    public double getRadius() {
112        return this.radius;
113    }
114
115    /**
116     * Sets the radius of the cap, as a percentage of the dial's framing
117     * rectangle, and sends a {@link DialLayerChangeEvent} to all registered
118     * listeners.
119     *
120     * @param radius  the radius (must be greater than zero).
121     *
122     * @see #getRadius()
123     */
124    public void setRadius(double radius) {
125        if (radius <= 0.0) {
126            throw new IllegalArgumentException("Requires radius > 0.0.");
127        }
128        this.radius = radius;
129        notifyListeners(new DialLayerChangeEvent(this));
130    }
131
132    /**
133     * Returns the paint used to fill the cap.
134     *
135     * @return The paint (never {@code null}).
136     *
137     * @see #setFillPaint(Paint)
138     */
139    public Paint getFillPaint() {
140        return this.fillPaint;
141    }
142
143    /**
144     * Sets the paint for the cap background and sends a
145     * {@link DialLayerChangeEvent} to all registered listeners.
146     *
147     * @param paint  the paint ({@code null} not permitted).
148     *
149     * @see #getFillPaint()
150     */
151    public void setFillPaint(Paint paint) {
152        Args.nullNotPermitted(paint, "paint");
153        this.fillPaint = paint;
154        notifyListeners(new DialLayerChangeEvent(this));
155    }
156
157    /**
158     * Returns the paint used to draw the outline of the cap.
159     *
160     * @return The paint (never {@code null}).
161     *
162     * @see #setOutlinePaint(Paint)
163     */
164    public Paint getOutlinePaint() {
165        return this.outlinePaint;
166    }
167
168    /**
169     * Sets the paint used to draw the outline of the cap and sends a
170     * {@link DialLayerChangeEvent} to all registered listeners.
171     *
172     * @param paint  the paint ({@code null} not permitted).
173     *
174     * @see #getOutlinePaint()
175     */
176    public void setOutlinePaint(Paint paint) {
177        Args.nullNotPermitted(paint, "paint");
178        this.outlinePaint = paint;
179        notifyListeners(new DialLayerChangeEvent(this));
180    }
181
182    /**
183     * Returns the stroke used to draw the outline of the cap.
184     *
185     * @return The stroke (never {@code null}).
186     *
187     * @see #setOutlineStroke(Stroke)
188     */
189    public Stroke getOutlineStroke() {
190        return this.outlineStroke;
191    }
192
193    /**
194     * Sets the stroke used to draw the outline of the cap and sends a
195     * {@link DialLayerChangeEvent} to all registered listeners.
196     *
197     * @param stroke  the stroke ({@code null} not permitted).
198     *
199     * @see #getOutlineStroke()
200     */
201    public void setOutlineStroke(Stroke stroke) {
202        Args.nullNotPermitted(stroke, "stroke");
203        this.outlineStroke = stroke;
204        notifyListeners(new DialLayerChangeEvent(this));
205    }
206
207    /**
208     * Returns {@code true} to indicate that this layer should be
209     * clipped within the dial window.
210     *
211     * @return {@code true}.
212     */
213    @Override
214    public boolean isClippedToWindow() {
215        return true;
216    }
217
218    /**
219     * Draws the background to the specified graphics device.  If the dial
220     * frame specifies a window, the clipping region will already have been
221     * set to this window before this method is called.
222     *
223     * @param g2  the graphics device ({@code null} not permitted).
224     * @param plot  the plot (ignored here).
225     * @param frame  the dial frame (ignored here).
226     * @param view  the view rectangle ({@code null} not permitted).
227     */
228    @Override
229    public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame,
230            Rectangle2D view) {
231
232        g2.setPaint(this.fillPaint);
233
234        Rectangle2D f = DialPlot.rectangleByRadius(frame, this.radius,
235                this.radius);
236        Ellipse2D e = new Ellipse2D.Double(f.getX(), f.getY(), f.getWidth(),
237                f.getHeight());
238        g2.fill(e);
239        g2.setPaint(this.outlinePaint);
240        g2.setStroke(this.outlineStroke);
241        g2.draw(e);
242
243    }
244
245    /**
246     * Tests this instance for equality with an arbitrary object.
247     *
248     * @param obj  the object ({@code null} permitted).
249     *
250     * @return A boolean.
251     */
252    @Override
253    public boolean equals(Object obj) {
254        if (obj == this) {
255            return true;
256        }
257        if (!(obj instanceof DialCap)) {
258            return false;
259        }
260        DialCap that = (DialCap) obj;
261        if (this.radius != that.radius) {
262            return false;
263        }
264        if (!PaintUtils.equal(this.fillPaint, that.fillPaint)) {
265            return false;
266        }
267        if (!PaintUtils.equal(this.outlinePaint, that.outlinePaint)) {
268            return false;
269        }
270        if (!this.outlineStroke.equals(that.outlineStroke)) {
271            return false;
272        }
273        return super.equals(obj);
274    }
275
276    /**
277     * Returns a hash code for this instance.
278     *
279     * @return The hash code.
280     */
281    @Override
282    public int hashCode() {
283        int result = 193;
284        result = 37 * result + HashUtils.hashCodeForPaint(this.fillPaint);
285        result = 37 * result + HashUtils.hashCodeForPaint(
286                this.outlinePaint);
287        result = 37 * result + this.outlineStroke.hashCode();
288        return result;
289    }
290
291    /**
292     * Returns a clone of this instance.
293     *
294     * @return A clone.
295     *
296     * @throws CloneNotSupportedException if some attribute of the cap cannot
297     *     be cloned.
298     */
299    @Override
300    public Object clone() throws CloneNotSupportedException {
301        return super.clone();
302    }
303
304    /**
305     * Provides serialization support.
306     *
307     * @param stream  the output stream.
308     *
309     * @throws IOException  if there is an I/O error.
310     */
311    private void writeObject(ObjectOutputStream stream) throws IOException {
312        stream.defaultWriteObject();
313        SerialUtils.writePaint(this.fillPaint, stream);
314        SerialUtils.writePaint(this.outlinePaint, stream);
315        SerialUtils.writeStroke(this.outlineStroke, stream);
316    }
317
318    /**
319     * Provides serialization support.
320     *
321     * @param stream  the input stream.
322     *
323     * @throws IOException  if there is an I/O error.
324     * @throws ClassNotFoundException  if there is a classpath problem.
325     */
326    private void readObject(ObjectInputStream stream)
327            throws IOException, ClassNotFoundException {
328        stream.defaultReadObject();
329        this.fillPaint = SerialUtils.readPaint(stream);
330        this.outlinePaint = SerialUtils.readPaint(stream);
331        this.outlineStroke = SerialUtils.readStroke(stream);
332    }
333
334}
335