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