001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2013, 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 * StandardDialFrame.java
029 * ----------------------
030 * (C) Copyright 2006-2013, by Object Refinery Limited.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   -;
034 *
035 * Changes
036 * -------
037 * 03-Nov-2006 : Version 1 (DG);
038 * 08-Mar-2007 : Fix in hashCode() (DG);
039 * 29-Oct-2007 : Renamed StandardDialFrame (DG);
040 * 03-Jul-2013 : Use ParamChecks (DG);
041 *
042 */
043
044package org.jfree.chart.plot.dial;
045
046import java.awt.BasicStroke;
047import java.awt.Color;
048import java.awt.Graphics2D;
049import java.awt.Paint;
050import java.awt.Shape;
051import java.awt.Stroke;
052import java.awt.geom.Area;
053import java.awt.geom.Ellipse2D;
054import java.awt.geom.Rectangle2D;
055import java.io.IOException;
056import java.io.ObjectInputStream;
057import java.io.ObjectOutputStream;
058import java.io.Serializable;
059
060import org.jfree.chart.HashUtilities;
061import org.jfree.chart.util.ParamChecks;
062import org.jfree.io.SerialUtilities;
063import org.jfree.util.PaintUtilities;
064import org.jfree.util.PublicCloneable;
065
066/**
067 * A simple circular frame for the {@link DialPlot} class.
068 *
069 * @since 1.0.7
070 */
071public class StandardDialFrame extends AbstractDialLayer implements DialFrame,
072        Cloneable, PublicCloneable, Serializable {
073
074    /** For serialization. */
075    static final long serialVersionUID = 1016585407507121596L;
076
077    /** The outer radius, relative to the framing rectangle. */
078    private double radius;
079
080    /**
081     * The color used for the front of the panel.  This field is transient
082     * because it requires special handling for serialization.
083     */
084    private transient Paint backgroundPaint;
085
086    /**
087     * The color used for the border around the window. This field is transient
088     * because it requires special handling for serialization.
089     */
090    private transient Paint foregroundPaint;
091
092    /**
093     * The stroke for drawing the frame outline.  This field is transient
094     * because it requires special handling for serialization.
095     */
096    private transient Stroke stroke;
097
098    /**
099     * Creates a new instance of <code>StandardDialFrame</code>.
100     */
101    public StandardDialFrame() {
102        this.backgroundPaint = Color.gray;
103        this.foregroundPaint = Color.black;
104        this.stroke = new BasicStroke(2.0f);
105        this.radius = 0.95;
106    }
107
108    /**
109     * Returns the radius, relative to the framing rectangle.
110     *
111     * @return The radius.
112     *
113     * @see #setRadius(double)
114     */
115    public double getRadius() {
116        return this.radius;
117    }
118
119    /**
120     * Sets the radius and sends a {@link DialLayerChangeEvent} to all
121     * registered listeners.
122     *
123     * @param radius  the radius (must be positive).
124     *
125     * @see #getRadius()
126     */
127    public void setRadius(double radius) {
128        if (radius <= 0) {
129            throw new IllegalArgumentException(
130                    "The 'radius' must be positive.");
131        }
132        this.radius = radius;
133        notifyListeners(new DialLayerChangeEvent(this));
134    }
135
136    /**
137     * Returns the background paint.
138     *
139     * @return The background paint (never <code>null</code>).
140     *
141     * @see #setBackgroundPaint(Paint)
142     */
143    public Paint getBackgroundPaint() {
144        return this.backgroundPaint;
145    }
146
147    /**
148     * Sets the background paint and sends a {@link DialLayerChangeEvent} to
149     * all registered listeners.
150     *
151     * @param paint  the paint (<code>null</code> not permitted).
152     *
153     * @see #getBackgroundPaint()
154     */
155    public void setBackgroundPaint(Paint paint) {
156        ParamChecks.nullNotPermitted(paint, "paint");
157        this.backgroundPaint = paint;
158        notifyListeners(new DialLayerChangeEvent(this));
159    }
160
161    /**
162     * Returns the foreground paint.
163     *
164     * @return The foreground paint (never <code>null</code>).
165     *
166     * @see #setForegroundPaint(Paint)
167     */
168    public Paint getForegroundPaint() {
169        return this.foregroundPaint;
170    }
171
172    /**
173     * Sets the foreground paint and sends a {@link DialLayerChangeEvent} to
174     * all registered listeners.
175     *
176     * @param paint  the paint (<code>null</code> not permitted).
177     *
178     * @see #getForegroundPaint()
179     */
180    public void setForegroundPaint(Paint paint) {
181        ParamChecks.nullNotPermitted(paint, "paint");
182        this.foregroundPaint = paint;
183        notifyListeners(new DialLayerChangeEvent(this));
184    }
185
186    /**
187     * Returns the stroke for the frame.
188     *
189     * @return The stroke (never <code>null</code>).
190     *
191     * @see #setStroke(Stroke)
192     */
193    public Stroke getStroke() {
194        return this.stroke;
195    }
196
197    /**
198     * Sets the stroke and sends a {@link DialLayerChangeEvent} to all
199     * registered listeners.
200     *
201     * @param stroke  the stroke (<code>null</code> not permitted).
202     *
203     * @see #getStroke()
204     */
205    public void setStroke(Stroke stroke) {
206        ParamChecks.nullNotPermitted(stroke, "stroke");
207        this.stroke = stroke;
208        notifyListeners(new DialLayerChangeEvent(this));
209    }
210
211    /**
212     * Returns the shape for the window for this dial.  Some dial layers will
213     * request that their drawing be clipped within this window.
214     *
215     * @param frame  the reference frame (<code>null</code> not permitted).
216     *
217     * @return The shape of the dial's window.
218     */
219    @Override
220    public Shape getWindow(Rectangle2D frame) {
221        Rectangle2D f = DialPlot.rectangleByRadius(frame, this.radius,
222                this.radius);
223        return new Ellipse2D.Double(f.getX(), f.getY(), f.getWidth(),
224                f.getHeight());
225    }
226
227    /**
228     * Returns <code>false</code> to indicate that this dial layer is not
229     * clipped to the dial window.
230     *
231     * @return A boolean.
232     */
233    @Override
234    public boolean isClippedToWindow() {
235        return false;
236    }
237
238    /**
239     * Draws the frame.  This method is called by the {@link DialPlot} class,
240     * you shouldn't need to call it directly.
241     *
242     * @param g2  the graphics target (<code>null</code> not permitted).
243     * @param plot  the plot (<code>null</code> not permitted).
244     * @param frame  the frame (<code>null</code> not permitted).
245     * @param view  the view (<code>null</code> not permitted).
246     */
247    @Override
248    public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame,
249            Rectangle2D view) {
250
251        Shape window = getWindow(frame);
252
253        Rectangle2D f = DialPlot.rectangleByRadius(frame, this.radius + 0.02,
254                this.radius + 0.02);
255        Ellipse2D e = new Ellipse2D.Double(f.getX(), f.getY(), f.getWidth(),
256                f.getHeight());
257
258        Area area = new Area(e);
259        Area area2 = new Area(window);
260        area.subtract(area2);
261        g2.setPaint(this.backgroundPaint);
262        g2.fill(area);
263
264        g2.setStroke(this.stroke);
265        g2.setPaint(this.foregroundPaint);
266        g2.draw(window);
267        g2.draw(e);
268    }
269
270    /**
271     * Tests this instance for equality with an arbitrary object.
272     *
273     * @param obj  the object (<code>null</code> permitted).
274     *
275     * @return A boolean.
276     */
277    @Override
278    public boolean equals(Object obj) {
279        if (obj == this) {
280            return true;
281        }
282        if (!(obj instanceof StandardDialFrame)) {
283            return false;
284        }
285        StandardDialFrame that = (StandardDialFrame) obj;
286        if (!PaintUtilities.equal(this.backgroundPaint, that.backgroundPaint)) {
287            return false;
288        }
289        if (!PaintUtilities.equal(this.foregroundPaint, that.foregroundPaint)) {
290            return false;
291        }
292        if (this.radius != that.radius) {
293            return false;
294        }
295        if (!this.stroke.equals(that.stroke)) {
296            return false;
297        }
298        return super.equals(obj);
299    }
300
301    /**
302     * Returns a hash code for this instance.
303     *
304     * @return The hash code.
305     */
306    @Override
307    public int hashCode() {
308        int result = 193;
309        long temp = Double.doubleToLongBits(this.radius);
310        result = 37 * result + (int) (temp ^ (temp >>> 32));
311        result = 37 * result + HashUtilities.hashCodeForPaint(
312                this.backgroundPaint);
313        result = 37 * result + HashUtilities.hashCodeForPaint(
314                this.foregroundPaint);
315        result = 37 * result + this.stroke.hashCode();
316        return result;
317    }
318
319    /**
320     * Returns a clone of this instance.
321     *
322     * @return A clone.
323     *
324     * @throws CloneNotSupportedException if any of the frame's attributes
325     *     cannot be cloned.
326     */
327    @Override
328    public Object clone() throws CloneNotSupportedException {
329        return super.clone();
330    }
331
332    /**
333     * Provides serialization support.
334     *
335     * @param stream  the output stream.
336     *
337     * @throws IOException  if there is an I/O error.
338     */
339    private void writeObject(ObjectOutputStream stream) throws IOException {
340        stream.defaultWriteObject();
341        SerialUtilities.writePaint(this.backgroundPaint, stream);
342        SerialUtilities.writePaint(this.foregroundPaint, stream);
343        SerialUtilities.writeStroke(this.stroke, stream);
344    }
345
346    /**
347     * Provides serialization support.
348     *
349     * @param stream  the input stream.
350     *
351     * @throws IOException  if there is an I/O error.
352     * @throws ClassNotFoundException  if there is a classpath problem.
353     */
354    private void readObject(ObjectInputStream stream)
355            throws IOException, ClassNotFoundException {
356        stream.defaultReadObject();
357        this.backgroundPaint = SerialUtilities.readPaint(stream);
358        this.foregroundPaint = SerialUtilities.readPaint(stream);
359        this.stroke = SerialUtilities.readStroke(stream);
360    }
361
362}