001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2007, 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, 2007, 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 */
067public class StandardDialFrame extends AbstractDialLayer implements DialFrame, 
068        Cloneable, PublicCloneable, Serializable {
069    
070    /** For serialization. */
071    static final long serialVersionUID = 1016585407507121596L;
072    
073    /** The outer radius, relative to the framing rectangle. */
074    private double radius;
075    
076    /**
077     * The color used for the front of the panel.  This field is transient
078     * because it requires special handling for serialization.
079     */
080    private transient Paint backgroundPaint;
081    
082    /**
083     * The color used for the border around the window. This field is transient
084     * because it requires special handling for serialization.
085     */
086    private transient Paint foregroundPaint;
087    
088    /**
089     * The stroke for drawing the frame outline.  This field is transient
090     * because it requires special handling for serialization.
091     */
092    private transient Stroke stroke;
093    
094    /**
095     * Creates a new instance of <code>StandardDialFrame</code>.
096     */
097    public StandardDialFrame() {
098        this.backgroundPaint = Color.gray;
099        this.foregroundPaint = Color.black;
100        this.stroke = new BasicStroke(2.0f);
101        this.radius = 0.95;   
102    }
103    
104    /**
105     * Returns the radius, relative to the framing 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 and sends a {@link DialLayerChangeEvent} to all 
117     * registered listeners.
118     *
119     * @param radius  the radius (must be positive).
120     * 
121     * @see #getRadius()
122     */
123    public void setRadius(double radius) {
124        if (radius <= 0) { 
125            throw new IllegalArgumentException(
126                    "The 'radius' must be positive.");
127        }
128        this.radius = radius;
129        notifyListeners(new DialLayerChangeEvent(this));
130    }
131
132    /**
133     * Returns the background paint.
134     * 
135     * @return The background paint (never <code>null</code>).
136     * 
137     * @see #setBackgroundPaint(Paint)
138     */
139    public Paint getBackgroundPaint() {
140        return this.backgroundPaint;
141    }
142    
143    /**
144     * Sets the background paint and sends a {@link DialLayerChangeEvent} to
145     * all registered listeners.
146     * 
147     * @param paint  the paint (<code>null</code> not permitted).
148     * 
149     * @see #getBackgroundPaint()
150     */
151    public void setBackgroundPaint(Paint paint) {
152        if (paint == null) {
153            throw new IllegalArgumentException("Null 'paint' argument.");
154        }
155        this.backgroundPaint = paint;
156        notifyListeners(new DialLayerChangeEvent(this));
157    }
158    
159    /**
160     * Returns the foreground paint.
161     * 
162     * @return The foreground paint (never <code>null</code>).
163     * 
164     * @see #setForegroundPaint(Paint)
165     */
166    public Paint getForegroundPaint() {
167        return this.foregroundPaint;
168    }
169    
170    /**
171     * Sets the foreground paint and sends a {@link DialLayerChangeEvent} to 
172     * all registered listeners.
173     * 
174     * @param paint  the paint (<code>null</code> not permitted).
175     * 
176     * @see #getForegroundPaint()
177     */
178    public void setForegroundPaint(Paint paint) {
179        if (paint == null) {
180            throw new IllegalArgumentException("Null 'paint' argument.");
181        }
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        if (stroke == null) { 
207            throw new IllegalArgumentException("Null 'stroke' argument.");
208        }
209        this.stroke = stroke;
210        notifyListeners(new DialLayerChangeEvent(this));
211    }
212        
213    /**
214     * Returns the shape for the window for this dial.  Some dial layers will
215     * request that their drawing be clipped within this window.
216     *
217     * @param frame  the reference frame (<code>null</code> not permitted).
218     *
219     * @return The shape of the dial's window.
220     */
221    public Shape getWindow(Rectangle2D frame) { 
222        Rectangle2D f = DialPlot.rectangleByRadius(frame, this.radius, 
223                this.radius);
224        return new Ellipse2D.Double(f.getX(), f.getY(), f.getWidth(), 
225                f.getHeight());  
226    }
227     
228    /**
229     * Returns <code>false</code> to indicate that this dial layer is not
230     * clipped to the dial window.
231     *
232     * @return A boolean.
233     */
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    public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame, 
248            Rectangle2D view) {
249        
250        Shape window = getWindow(frame);
251        
252        Rectangle2D f = DialPlot.rectangleByRadius(frame, this.radius + 0.02, 
253                this.radius + 0.02);
254        Ellipse2D e = new Ellipse2D.Double(f.getX(), f.getY(), f.getWidth(), 
255                f.getHeight());
256        
257        Area area = new Area(e);
258        Area area2 = new Area(window);
259        area.subtract(area2);
260        g2.setPaint(this.backgroundPaint);
261        g2.fill(area);
262        
263        g2.setStroke(this.stroke);
264        g2.setPaint(this.foregroundPaint);
265        g2.draw(window);
266        g2.draw(e);
267    }
268
269    /**
270     * Tests this instance for equality with an arbitrary object.
271     *
272     * @param obj  the object (<code>null</code> permitted).
273     *
274     * @return A boolean.
275     */
276    public boolean equals(Object obj) {
277        if (obj == this) {
278            return true;
279        }
280        if (!(obj instanceof StandardDialFrame)) {
281            return false;
282        }
283        StandardDialFrame that = (StandardDialFrame) obj;
284        if (!PaintUtilities.equal(this.backgroundPaint, that.backgroundPaint)) {
285            return false;
286        }
287        if (!PaintUtilities.equal(this.foregroundPaint, that.foregroundPaint)) {
288            return false;
289        }
290        if (this.radius != that.radius) {
291            return false;
292        }
293        if (!this.stroke.equals(that.stroke)) {
294            return false;
295        }
296        return super.equals(obj);
297    }
298    
299    /**
300     * Returns a hash code for this instance.
301     * 
302     * @return The hash code.
303     */
304    public int hashCode() {
305        int result = 193;
306        long temp = Double.doubleToLongBits(this.radius);
307        result = 37 * result + (int) (temp ^ (temp >>> 32));
308        result = 37 * result + HashUtilities.hashCodeForPaint(
309                this.backgroundPaint);
310        result = 37 * result + HashUtilities.hashCodeForPaint(
311                this.foregroundPaint);
312        result = 37 * result + this.stroke.hashCode();
313        return result;
314    }
315    
316    /**
317     * Returns a clone of this instance.
318     *
319     * @return A clone.
320     * 
321     * @throws CloneNotSupportedException if any of the frame's attributes 
322     *     cannot be cloned.
323     */
324    public Object clone() throws CloneNotSupportedException {
325        return super.clone();
326    }
327    
328    /**
329     * Provides serialization support.
330     *
331     * @param stream  the output stream.
332     *
333     * @throws IOException  if there is an I/O error.
334     */
335    private void writeObject(ObjectOutputStream stream) throws IOException {
336        stream.defaultWriteObject();
337        SerialUtilities.writePaint(this.backgroundPaint, stream);
338        SerialUtilities.writePaint(this.foregroundPaint, stream);
339        SerialUtilities.writeStroke(this.stroke, stream);
340    }
341
342    /**
343     * Provides serialization support.
344     *
345     * @param stream  the input stream.
346     *
347     * @throws IOException  if there is an I/O error.
348     * @throws ClassNotFoundException  if there is a classpath problem.
349     */
350    private void readObject(ObjectInputStream stream) 
351            throws IOException, ClassNotFoundException {
352        stream.defaultReadObject();
353        this.backgroundPaint = SerialUtilities.readPaint(stream);
354        this.foregroundPaint = SerialUtilities.readPaint(stream);
355        this.stroke = SerialUtilities.readStroke(stream);
356    }
357    
358}