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